mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

132 files modified
5 files added
3060 ■■■■ changed files
opendj-config/src/main/java/org/forgerock/opendj/config/client/MissingMandatoryPropertiesException.java 3 ●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java 8 ●●●●● patch | view | raw | blame | history
opendj-config/src/test/java/org/forgerock/opendj/config/dsconfig/BuildVersionTest.java 46 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/opendj/util/ASCIICharProp.java 5 ●●●●● patch | view | raw | blame | history
opendj-core/src/main/java/com/forgerock/opendj/util/StringPrepProfile.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/AttributeFilter.java 15 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/GeneralizedTime.java 6 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/InetAddressValidator.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/PasswordPolicyResponseControl.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaUtils.java 21 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/UTCTimeSyntaxImpl.java 13 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java 3 ●●●● patch | view | raw | blame | history
opendj-core/src/main/java/org/forgerock/opendj/security/OpenDJProvider.java 4 ●●● patch | view | raw | blame | history
opendj-core/src/test/java/org/forgerock/opendj/ldap/AttributeFilterTestCase.java 63 ●●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateConfigurationReferenceMojo.java 3 ●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateGlobalAcisTableMojo.java 39 ●●●●● patch | view | raw | blame | history
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/Utils.java 21 ●●●●● patch | view | raw | blame | history
opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java 2 ●●● patch | view | raw | blame | history
opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java 98 ●●●●● patch | view | raw | blame | history
opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/EmbeddedOpenDJ.java 299 ●●●● patch | view | raw | blame | history
opendj-embedded/src/test/java/org/openidentityplatform/opendj/embedded/ConfigTest.java 124 ●●●●● patch | view | raw | blame | history
opendj-embedded/src/test/java/org/openidentityplatform/opendj/embedded/EmbeddedOpenDJCleanupTest.java 181 ●●●●● patch | view | raw | blame | history
opendj-embedded/src/test/java/org/openidentityplatform/opendj/embedded/EmbeddedOpenDJTest.java 94 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ModifyAsync.java 17 ●●●●● patch | view | raw | blame | history
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java 6 ●●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Base64.java 11 ●●●● patch | view | raw | blame | history
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java 4 ●●●● patch | view | raw | blame | history
opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateConfigMojo.java 4 ●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java 5 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/replace.rb 4 ●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java 56 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/browser/BrowserController.java 5 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/browser/NodeSearcherQueue.java 22 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/task/Task.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/BrowseEntriesPanel.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/GenericDialog.java 5 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/IndexPanel.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/StatusGenericPanel.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/components/CustomTree.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/components/FilterTextField.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/guitools/uninstaller/Uninstaller.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/BuildInformation.java 6 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.java 11 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/InstallerHelper.java 17 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/ui/RuntimeOptionsPanel.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/OutputReader.java 33 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ServerController.java 46 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/Utils.java 52 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ZipExtractor.java 9 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/api/ConnectionHandler.java 16 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/AciHandler.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java 12 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/UserAttr.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java 8 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2URI.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java 64 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.java 4 ●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java 10 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskThread.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/config/ConfigurationHandler.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java 6 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/MemoryQuota.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/ModifyDNOperationBasis.java 32 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/SearchOperationBasis.java 5 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/DynamicGroup.java 7 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/DynamicGroupMemberList.java 19 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/EntryCacheCommon.java 7 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/ExternalSASLMechanismHandler.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java 9 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java 9 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/extensions/StaticGroup.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/AsynchronousTextWriter.java 20 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/CommonAudit.java 2 ●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/CommonAuditAccessLogPublisher.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/JDKLogging.java 9 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/MultifileTextWriter.java 16 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextAccessLogPublisher.java 6 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextAuditLogPublisher.java 6 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextDebugLogPublisher.java 6 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextErrorLogPublisher.java 6 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextHTTPAccessLogPublisher.java 6 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/plugins/PasswordPolicyImportPlugin.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/plugins/profiler/ProfileViewer.java 14 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/plugins/profiler/ProfilerPlugin.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java 51 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/common/StatusMachine.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalSingle.java 7 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/ReplicationServerListener.java 26 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/MessageHandler.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/ReplicationServer.java 411 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/ReplicationServerDomain.java 15 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java 2 ●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ChangeNumberIndexer.java 7 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/FileChangelogDB.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/LogFile.java 15 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java 8 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java 2 ●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/schema/GeneralizedTimeSyntax.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java 19 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tasks/InitializeTask.java 19 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tasks/PurgeConflictsHistoricalTask.java 11 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureWindowsService.java 68 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnection.java 3 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/dsreplication/ReplicationCliMain.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/makeldif/MakeLDIFInputStream.java 26 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/status/StatusCli.java 20 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java 25 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/LDAPURL.java 3 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/LDIFImportConfig.java 24 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/RecordingInputStream.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/util/CronExecutorService.java 13 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/util/StaticUtils.java 11 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java 7 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/messages/org/opends/messages/protocol.properties 4 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/messages/org/opends/messages/replication.properties 10 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/snmp/src/org/opends/server/snmp/DsMIBImpl.java 6 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java 57 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/JebTestCase.java 3 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java 97 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/protocols/ldap/TestLDAPConnectionHandler.java 53 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/replication/server/ReplicationServerDynamicConfTest.java 206 ●●●●● patch | view | raw | blame | history
opendj-server/src/main/java/org/forgerock/opendj/server/core/ProductInformation.java 3 ●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/client/MissingMandatoryPropertiesException.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.config.client;
@@ -99,7 +100,7 @@
     * @return Returns the first exception that caused this exception.
     */
    @Override
    public PropertyException getCause() {
    public synchronized PropertyException getCause() {
        return causes.iterator().next();
    }
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.config.dsconfig;
@@ -191,11 +192,8 @@
        if (major == version.major) {
            if (minor == version.minor) {
                if (point == version.point) {
                    if (rev == version.rev) {
                        return 0;
                    } else if (rev.compareTo(version.rev) < 0) {
                        return -1;
                    }
                    // Compares the revisions as strings: they are VCS revisions, not numbers.
                    return Integer.signum(rev.compareTo(version.rev));
                } else if (point < version.point) {
                    return -1;
                }
opendj-config/src/test/java/org/forgerock/opendj/config/dsconfig/BuildVersionTest.java
New file
@@ -0,0 +1,46 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.config.dsconfig;
import org.forgerock.testng.ForgeRockTestCase;
import org.testng.Assert;
import org.testng.annotations.Test;
@Test(groups = { "precommit", "config" })
public class BuildVersionTest extends ForgeRockTestCase {
    @Test
    public void testEqualRevisionsCompareEqual() {
        // Two distinct String instances holding the same revision: the revisions must be compared
        // by value, not by reference.
        final String rev = "abcdef";
        final String sameRev = new StringBuilder(rev).toString();
        Assert.assertEquals(new BuildVersion(4, 0, 0, rev).compareTo(new BuildVersion(4, 0, 0, sameRev)), 0);
    }
    @Test
    public void testRevisionsOrderVersionsWhichAreOtherwiseEqual() {
        Assert.assertEquals(new BuildVersion(4, 0, 0, "aaa").compareTo(new BuildVersion(4, 0, 0, "bbb")), -1);
        Assert.assertEquals(new BuildVersion(4, 0, 0, "bbb").compareTo(new BuildVersion(4, 0, 0, "aaa")), 1);
    }
    @Test
    public void testVersionNumbersTakePrecedenceOverRevisions() {
        Assert.assertEquals(new BuildVersion(3, 9, 9, "zzz").compareTo(new BuildVersion(4, 0, 0, "aaa")), -1);
        Assert.assertEquals(new BuildVersion(4, 1, 0, "aaa").compareTo(new BuildVersion(4, 0, 9, "zzz")), 1);
        Assert.assertEquals(new BuildVersion(4, 0, 1, "aaa").compareTo(new BuildVersion(4, 0, 0, "zzz")), 1);
    }
}
opendj-core/src/main/java/com/forgerock/opendj/util/ASCIICharProp.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2009 Sun Microsystems, Inc.
 * Portions copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package com.forgerock.opendj.util;
@@ -110,7 +111,7 @@
            this.isKeyChar = true;
            this.isCompatKeyChar = true;
            this.decimalValue = -1;
            if (c >= 'a' && c <= 'f') {
            if (c <= 'f') {
                this.isHexChar = true;
                this.hexValue = c - 87;
            } else {
@@ -127,7 +128,7 @@
            this.isKeyChar = true;
            this.isCompatKeyChar = true;
            this.decimalValue = -1;
            if (c >= 'A' && c <= 'F') {
            if (c <= 'F') {
                this.isHexChar = true;
                this.hexValue = c - 55;
            } else {
opendj-core/src/main/java/com/forgerock/opendj/util/StringPrepProfile.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions Copyright 2015 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package com.forgerock.opendj.util;
@@ -462,7 +463,7 @@
                    if (canMapToSpace(buffer, trim)) {
                        buffer.append(SPACE_CHAR);
                    }
                } else if ((b >= '\u0000' && b <= '\u0008') || (b >= '\u000E' && b <= '\u001F')
                } else if (b <= '\u0008' || (b >= '\u000E' && b <= '\u001F')
                        || b == '\u007F') {
                    // These characters are mapped to nothing and hence not
                    // copied over..
opendj-core/src/main/java/org/forgerock/opendj/ldap/AVA.java
@@ -166,9 +166,10 @@
                        builder.append(StaticUtils.byteToLowerHex(b));
                    }
                } else {
                    // NUL needs no test here: it is already handled by the c < ' ' branch above.
                    if ((c == ' ' && si == length - 1)
                            || (c == '"' || c == '+' || c == ',' || c == ';' || c == '<'
                            || c == '>' || c == '\\' || c == '\u0000' || c == '=')) {
                            || c == '>' || c == '\\' || c == '=')) {
                        builder.append('\\');
                    }
                    builder.append(c);
opendj-core/src/main/java/org/forgerock/opendj/ldap/AttributeFilter.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.ldap;
@@ -163,13 +164,19 @@
                 * requested by the user.
                 */
                return new Iterable<Attribute>() {
                    private boolean hasNextMustIterate = true;
                    private final Iterator<Attribute> iterator = entry.getAllAttributes().iterator();
                    private Attribute next = null;
                    @Override
                    public Iterator<Attribute> iterator() {
                        /*
                         * The iteration state belongs to the iterator, not to this Iterable:
                         * keeping it here is what allows the Iterable to be iterated more than
                         * once, including by the toString() below.
                         */
                        return new Iterator<Attribute>() {
                            private boolean hasNextMustIterate = true;
                            private final Iterator<Attribute> iterator =
                                    entry.getAllAttributes().iterator();
                            private Attribute next;
                            @Override
                            public boolean hasNext() {
                                if (hasNextMustIterate) {
opendj-core/src/main/java/org/forgerock/opendj/ldap/GeneralizedTime.java
@@ -836,9 +836,9 @@
    @Override
    public int compareTo(final GeneralizedTime o) {
        final Long timeMS1 = getTimeInMillis();
        final Long timeMS2 = o.getTimeInMillis();
        return timeMS1.compareTo(timeMS2);
        final long timeMS1 = getTimeInMillis();
        final long timeMS2 = o.getTimeInMillis();
        return Long.compare(timeMS1, timeMS2);
    }
    @Override
opendj-core/src/main/java/org/forgerock/opendj/ldap/InetAddressValidator.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Portions copyright 2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
@@ -184,7 +185,7 @@
                    if (!inet6Address.endsWith(octet)) {
                        return false;
                    }
                    if (index > octets.length - 1 || index > 6) {  // CHECKSTYLE IGNORE MagicNumber
                    if (index > 6) {  // CHECKSTYLE IGNORE MagicNumber
                        // IPV4 occupies last two octets
                        return false;
                    }
opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/PasswordPolicyResponseControl.java
@@ -138,8 +138,7 @@
                            // nested element.
                            reader.readStartSequence();
                            final int warningChoiceValue = (0x7F & reader.peekType());
                            if (warningChoiceValue < 0
                                    || warningChoiceValue >= PasswordPolicyWarningType.values().length) {
                            if (warningChoiceValue >= PasswordPolicyWarningType.values().length) {
                                final LocalizableMessage message =
                                        ERR_PWPOLICYRES_INVALID_WARNING_TYPE.get(byteToHex(reader
                                                .peekType()));
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/NameForm.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2009 Sun Microsystems, Inc.
 * Portions Copyright 2026 3A Systems, LLC.
 * Portions copyright 2011-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.schema;
@@ -334,7 +335,7 @@
        if (builder.structuralObjectClassOID == null || builder.structuralObjectClassOID.isEmpty()) {
            throw new IllegalArgumentException("A structural class OID must be specified.");
        }
        if (builder.requiredAttributes == null || builder.requiredAttributes.isEmpty()) {
        if (builder.requiredAttributes.isEmpty()) {
            throw new IllegalArgumentException("Required attribute must be specified.");
        }
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/SchemaUtils.java
@@ -13,7 +13,7 @@
 *
 * Copyright 2009 Sun Microsystems, Inc.
 * Portions copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2024 3A Systems, LLC.
 * Portions Copyright 2024-2026 3A Systems, LLC.
 */
package org.forgerock.opendj.ldap.schema;
@@ -229,11 +229,8 @@
                        && (c = reader.read()) != ' '
                        && c != ')'
                        && (c != '\'' || !enclosingQuote)) {
                    if (length == 0 && !isAlpha(c)) {
                        throw DecodeException.error(
                                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID1.get(c, reader.pos() - 1));
                    }
                    // The first character was already required to be alphabetic by the enclosing
                    // test, so only the key-character check is needed here.
                    if (!isKeyChar(c, allowCompatChars)) {
                        throw DecodeException.error(
                                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID1.get(c, reader.pos() - 1));
@@ -326,11 +323,6 @@
                    }
                    length++;
                }
                if (length == 0) {
                    throw DecodeException.error(
                            ERR_ATTR_SYNTAX_OID_NO_VALUE1.get(reader.pos() - 1));
                }
            } else if (isAlpha(c)) {
                // This must be an attribute description. In this case, we will
                // only accept alphabetic characters, numeric digits, and the hyphen.
@@ -338,11 +330,8 @@
                        && c != ')'
                        && c != '{'
                        && (c != '\'' || !enclosingQuote)) {
                    if (length == 0 && !isAlpha(c)) {
                        throw DecodeException.error(
                                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID1.get(c, reader.pos() - 1));
                    }
                    // The first character was already required to be alphabetic by the enclosing
                    // test, so only the key-character check is needed here.
                    if (!isKeyChar(c, allowCompatChars)) {
                        throw DecodeException.error(
                                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID1.get(c, reader.pos() - 1));
opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/UTCTimeSyntaxImpl.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2009 Sun Microsystems, Inc.
 * Portions Copyright 2026 3A Systems, LLC.
 * Portions copyright 2012-2016 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.schema;
@@ -399,16 +400,8 @@
        case '3':
        case '4':
        case '5':
            // There must be at least two more characters, and the next one
            // must be a digit between 0 and 9.
            if (length < 11) {
                final LocalizableMessage message =
                        ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String.valueOf(m1),
                                8);
                invalidReason.append(message);
                return false;
            }
            // A length below 11 was already rejected at the top of this method, so the next
            // character is present and must be a digit between 0 and 9.
            switch (valueString.charAt(9)) {
            case '0':
            case '1':
opendj-core/src/main/java/org/forgerock/opendj/ldif/TemplateTag.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2013-2015 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.ldif;
@@ -1322,7 +1323,7 @@
        }
        private void initialize(String[] arguments, int lineNumber) throws DecodeException {
            if (arguments.length < 0 || arguments.length > 2) {
            if (arguments.length > 2) {
                throw DecodeException.fatalError(ERR_ENTRY_GENERATOR_TAG_INVALID_ARGUMENT_RANGE_COUNT.get(
                        getName(), lineNumber, 0, 2, arguments.length));
            }
opendj-core/src/main/java/org/forgerock/opendj/security/OpenDJProvider.java
@@ -12,6 +12,7 @@
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.security;
@@ -210,7 +211,8 @@
    }
    private static ConnectionFactory newLDIFConnectionFactory(final File ldifFile) throws IOException {
        try (LDIFEntryReader reader = new LDIFEntryReader(new FileReader(ldifFile)).setSchema(SCHEMA)) {
        try (FileReader ldifFileReader = new FileReader(ldifFile);
                LDIFEntryReader reader = new LDIFEntryReader(ldifFileReader).setSchema(SCHEMA)) {
            final MemoryBackend backend = new MemoryBackend(SCHEMA, reader).enableVirtualAttributes(true);
            return newInternalConnectionFactory(new WriteLDIFOnUpdateRequestHandler(backend, ldifFile));
        }
opendj-core/src/test/java/org/forgerock/opendj/ldap/AttributeFilterTestCase.java
New file
@@ -0,0 +1,63 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Assertions.assertThat;
import java.util.ArrayList;
import java.util.List;
import org.testng.annotations.Test;
/**
 * Test {@code AttributeFilter}.
 */
@SuppressWarnings("javadoc")
public final class AttributeFilterTestCase extends SdkTestCase {
    @Test
    public void testFilteredViewCanBeIteratedMoreThanOnce() {
        final Iterable<Attribute> attributes = filteredView().getAllAttributes();
        final List<String> firstPass = namesOf(attributes);
        assertThat(firstPass).isNotEmpty();
        assertThat(namesOf(attributes)).isEqualTo(firstPass);
    }
    @Test
    public void testFilteredViewCanBeIteratedAfterToString() {
        final Iterable<Attribute> attributes = filteredView().getAllAttributes();
        final List<String> expected = namesOf(attributes);
        attributes.toString();
        assertThat(namesOf(attributes)).isEqualTo(expected);
    }
    private Entry filteredView() {
        final Entry entry =
                new LinkedHashMapEntry("dn: cn=test", "objectClass: top", "objectClass: person",
                        "cn: test", "sn: user");
        return new AttributeFilter().filteredViewOf(entry);
    }
    private List<String> namesOf(final Iterable<Attribute> attributes) {
        final List<String> names = new ArrayList<>();
        for (final Attribute attribute : attributes) {
            names.add(attribute.getAttributeDescriptionAsString());
        }
        return names;
    }
}
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateConfigurationReferenceMojo.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.maven.doc;
@@ -151,7 +152,7 @@
     */
    private void copyResources() throws IOException {
        for (String file : resourceFiles) {
            InputStream original = this.getClass().getResourceAsStream("/config-ref/" + file);
            InputStream original = GenerateConfigurationReferenceMojo.class.getResourceAsStream("/config-ref/" + file);
            File copy = new File(outputDirectory, file);
            copyInputStreamToFile(original, copy);
        }
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/GenerateGlobalAcisTableMojo.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.maven.doc;
@@ -102,12 +103,14 @@
     */
    private Map<String, String> getAciDescriptions() throws IOException {
        final Map<String, String> descriptions = new HashMap<>();
        BufferedReader reader = new BufferedReader(new FileReader(configDotLdif));
        String line;
        while ((line = reader.readLine()) != null) {
            if (line.startsWith("# @aci ")) {
                String[] split = line.replace("# @aci ", "").split(":", 2);
                descriptions.put(split[0], split[1]);
        try (FileReader fileReader = new FileReader(configDotLdif);
             BufferedReader reader = new BufferedReader(fileReader)) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("# @aci ")) {
                    String[] split = line.replace("# @aci ", "").split(":", 2);
                    descriptions.put(split[0], split[1]);
                }
            }
        }
        return descriptions;
@@ -120,19 +123,21 @@
     * @throws IOException  Failed to read the LDIF.
     */
    private void readAcis(Map<String, String> descriptions) throws IOException {
        LDIFEntryReader reader = new LDIFEntryReader(new FileInputStream(configDotLdif));
        reader.setIncludeBranch(DN.valueOf("cn=Access Control Handler,cn=config"));
        try (FileInputStream configStream = new FileInputStream(configDotLdif);
             LDIFEntryReader reader = new LDIFEntryReader(configStream)) {
            reader.setIncludeBranch(DN.valueOf("cn=Access Control Handler,cn=config"));
        while (reader.hasNext()) {
            Entry entry = reader.readEntry();
            for (String attribute : entry.parseAttribute("ds-cfg-global-aci").asSetOfString()) {
                Aci aci = new Aci();
                aci.name = getName(attribute);
                if (descriptions != null) {
                    aci.description = descriptions.get(aci.name);
            while (reader.hasNext()) {
                Entry entry = reader.readEntry();
                for (String attribute : entry.parseAttribute("ds-cfg-global-aci").asSetOfString()) {
                    Aci aci = new Aci();
                    aci.name = getName(attribute);
                    if (descriptions != null) {
                        aci.description = descriptions.get(aci.name);
                    }
                    aci.definition = AsciidocConverterUtils.escapeVerticalLine(attribute);
                    allGlobalAcis.add(aci);
                }
                aci.definition = AsciidocConverterUtils.escapeVerticalLine(attribute);
                allGlobalAcis.add(aci);
            }
        }
    }
opendj-doc-maven-plugin/src/main/java/org/forgerock/opendj/maven/doc/Utils.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.maven.doc;
@@ -75,11 +76,15 @@
     * @throws IOException  Failed to make the copy.
     */
    static void copyFile(File original, File copy) throws IOException {
        copyInputStreamToFile(new FileInputStream(original), copy);
        try (InputStream input = new FileInputStream(original)) {
            copyInputStreamToFile(input, copy);
        }
    }
    /**
     * Copies the content of the original input stream to the copy.
     * <p>
     * The original input stream is closed on return, whether the copy succeeded or not.
     * @param original      The original input stream.
     * @param copy          The copy.
     * @throws IOException  Failed to make the copy.
@@ -88,12 +93,14 @@
        if (original == null) {
            throw new IOException("Could not read input to copy.");
        }
        createFile(copy);
        try (OutputStream outputStream = new FileOutputStream(copy)) {
            int bytesRead;
            byte[] buffer = new byte[4096];
            while ((bytesRead = original.read(buffer)) > 0) {
                outputStream.write(buffer, 0, bytesRead);
        try {
            createFile(copy);
            try (OutputStream outputStream = new FileOutputStream(copy)) {
                int bytesRead;
                byte[] buffer = new byte[4096];
                while ((bytesRead = original.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        } finally {
            closeSilently(original);
opendj-dsml-servlet/src/main/java/org/opends/dsml/protocol/DSMLServlet.java
@@ -234,7 +234,7 @@
      // assign the DSMLv2 schema for validation
      if(schema==null)
      {
        URL url = getClass().getResource("/resources/DSMLv2.xsd");
        URL url = DSMLServlet.class.getResource("/resources/DSMLv2.xsd");
        if ( url != null ) {
          SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
          schema = sf.newSchema(url);
opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java
@@ -11,7 +11,7 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2024 3A Systems LLC.
 * Copyright 2024-2026 3A Systems LLC.
 */
package org.openidentityplatform.opendj.embedded;
@@ -23,9 +23,9 @@
public class Config {
    private final String CONFIG_PREFIX = Config.class.getPackage().getName();
    private int port = Integer.parseInt(System.getProperty(CONFIG_PREFIX + ".port", "1389"));
    private int port = intProperty(CONFIG_PREFIX + ".port", "1389");
    private int adminPort = Integer.parseInt(System.getProperty(CONFIG_PREFIX + ".admin_port", "4444"));
    private int adminPort = intProperty(CONFIG_PREFIX + ".admin_port", "4444");
    private String adminPassword = System.getProperty(CONFIG_PREFIX + ".password", "passw0rd");
@@ -33,7 +33,7 @@
    private String backendType = System.getProperty(CONFIG_PREFIX + ".backend", "je");
    private int jmxPort = Integer.parseInt(System.getProperty(CONFIG_PREFIX + ".jmx_port", "1689"));
    private int jmxPort = intProperty(CONFIG_PREFIX + ".jmx_port", "1689");
    private String ldifSchema = System.getProperty(CONFIG_PREFIX + ".ldif.schema");
@@ -41,6 +41,64 @@
    private Set<String> skipSet = new HashSet<>(Arrays.asList(System.getProperty(CONFIG_PREFIX + ".skip", ",ou=sample-skip-group,").toLowerCase().split(";")));
    private long deleteTimeout = timeoutProperty(CONFIG_PREFIX + ".delete_timeout", "10000");
    /**
     * Returns the value of a system property holding a number.
     * <p>
     * These fields are initialized when the instance is created, so an unhandled
     * {@link NumberFormatException} would surface from the constructor of
     * {@link EmbeddedOpenDJ} saying only which string was rejected, without naming the
     * property that carried it.
     *
     * @param name
     *            the name of the system property
     * @param defaultValue
     *            the value used when the property is not set
     * @return the value of the property, or {@code defaultValue} when it is not set
     * @throws IllegalArgumentException
     *             If the value of the property is not a number.
     */
    private static int intProperty(String name, String defaultValue) {
        final String value = System.getProperty(name, defaultValue);
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw invalidProperty(name, value, e);
        }
    }
    /**
     * Returns the value of a system property holding a duration in milliseconds.
     *
     * @param name
     *            the name of the system property
     * @param defaultValue
     *            the value used when the property is not set
     * @return the value of the property, or {@code defaultValue} when it is not set
     * @throws IllegalArgumentException
     *             If the value of the property is not a number, or is negative.
     */
    private static long timeoutProperty(String name, String defaultValue) {
        final String value = System.getProperty(name, defaultValue);
        final long timeout;
        try {
            timeout = Long.parseLong(value);
        } catch (NumberFormatException e) {
            throw invalidProperty(name, value, e);
        }
        if (timeout < 0) {
            throw new IllegalArgumentException("Invalid value \"" + value + "\" for the system property "
                    + name + ": a timeout in milliseconds cannot be negative");
        }
        return timeout;
    }
    private static IllegalArgumentException invalidProperty(String name, String value, NumberFormatException cause) {
        return new IllegalArgumentException("Invalid value \"" + value + "\" for the system property "
                + name + ": expected a number", cause);
    }
    public int getPort() {
        return port;
    }
@@ -115,6 +173,37 @@
        this.skipSet = skipSet;
    }
    /**
     * Returns how long {@link EmbeddedOpenDJ#close()} retries the deletion of the temporary
     * directory of the instance, in milliseconds.
     * <p>
     * The deletion has to be retried because a file cannot be deleted on Windows while a
     * handle to it is still open, and the server threads release their handles shortly after
     * the server has been stopped. Whatever is still locked when this expires is scheduled for
     * deletion on JVM exit. Set it to {@code 0} to delete once and never wait.
     *
     * @return the deletion timeout in milliseconds
     */
    public long getDeleteTimeout() {
        return deleteTimeout;
    }
    /**
     * Sets how long {@link EmbeddedOpenDJ#close()} retries the deletion of the temporary
     * directory of the instance.
     *
     * @param deleteTimeout
     *            the deletion timeout in milliseconds, {@code 0} to delete once and never wait
     * @throws IllegalArgumentException
     *             If the timeout is negative.
     */
    public void setDeleteTimeout(long deleteTimeout) {
        if (deleteTimeout < 0) {
            throw new IllegalArgumentException("The delete timeout cannot be negative, but was " + deleteTimeout);
        }
        this.deleteTimeout = deleteTimeout;
    }
    @Override
    public String toString() {
        return "Config {" +
@@ -127,6 +216,7 @@
                ", ldifSchema='" + ldifSchema + '\'' +
                ", file='" + file + '\'' +
                ", skipSet=" + skipSet +
                ", deleteTimeout=" + deleteTimeout +
                '}';
    }
}
opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/EmbeddedOpenDJ.java
@@ -51,10 +51,30 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
public class EmbeddedOpenDJ implements Runnable, Closeable {
    private static final String JAR_SCHEMA_DIRECTORY = "opendj/config/schema/";
    private static final String ARCHIVE_NAME = "opendj.zip";
    /**
     * How long the deletion of the instance directory is retried when there is no point in
     * waiting for the full {@link Config#getDeleteTimeout() configured timeout}: from the
     * shutdown hook, where a longer wait only makes Control-C look hung, and from the
     * constructor, where an initialization failure has to be reported promptly. What is still
     * locked when this expires is deleted on JVM exit anyway.
     */
    private static final long SHORT_DELETE_TIMEOUT_MS = 1_000L;
    private static final long INITIAL_DELETE_RETRY_DELAY_MS = 50L;
    private static final long MAX_DELETE_RETRY_DELAY_MS = 500L;
    /** Upper bound on the number of leftover paths reported when the deletion fails. */
    private static final int MAX_REPORTED_REMAINING_PATHS = 20;
    final static Logger logger = LoggerFactory.getLogger(EmbeddedOpenDJ.class.getName());
    final EmbeddedDirectoryServer server;
@@ -62,6 +82,10 @@
    private final File instanceDirectory;
    private final File rootDirectory;
    private final Thread shutdownHook;
    /** Written under {@code this}, read without locking so that a running {@link #close()} blocks nothing. */
    private volatile boolean closed;
    public EmbeddedOpenDJ() {
        this(new Config());
@@ -82,7 +106,10 @@
            // deleted on close().
            instanceDirectory = Files.createTempDirectory("opendj").toFile();
            File rootDirectory = new File(instanceDirectory, "opendj");
            rootDirectory.mkdir();
            if (!rootDirectory.mkdir()) {
                // the parent has just been created, so this only fails on a real filesystem error
                throw new IOException("Cannot create the server root directory " + rootDirectory);
            }
            logger.info("OpenDJ server root: {}", rootDirectory);
            File configDirectory = new File(rootDirectory, "config");
@@ -100,8 +127,15 @@
                    System.out,
                    System.err);
            copyFilesFromJar(Collections.singletonList("opendj.zip"),"embedded-opendj/",rootDirectory);
            server.extractArchiveForSetup(new File(rootDirectory,"opendj.zip"));
            copyFilesFromJar(Collections.singletonList(ARCHIVE_NAME),"embedded-opendj/",rootDirectory);
            final File archive = new File(rootDirectory, ARCHIVE_NAME);
            server.extractArchiveForSetup(archive);
            // The archive is only needed for the extraction above. Keeping it would leave a
            // full copy of the distribution in the temporary directory and one more file to
            // delete on close().
            if (!archive.delete()) {
                logger.warn("Cannot delete {} after extracting it", archive);
            }
            server.setup(
                    SetupParameters.setupParams()
@@ -121,32 +155,35 @@
            this.rootDirectory = rootDirectory;
        }catch (Exception e) {
            logger.error("Error initializing OpenDJ");
            FileUtils.deleteQuietly(instanceDirectory);
            deleteInstanceDirectory(instanceDirectory, shortDeleteTimeout(config));
            throw new RuntimeException(e);
        }
        Runtime.getRuntime().addShutdownHook(new Thread(this::close));
        shutdownHook = new Thread(this::close, "EmbeddedOpenDJ shutdown hook");
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
    /**
     * Returns the server root directory of this embedded instance.
     *
     * @return the server root directory
     * @throws IllegalStateException
     *             If this instance has been closed, and the directory therefore deleted.
     */
    public File getServerRootDirectory() {
        checkNotClosed();
        return rootDirectory;
    }
    @Override
    public void run() {
        checkNotClosed();
        try {
            final DN baseDN = DN.valueOf(config.getBaseDN());
            try {
                ManagementContext config = server.getConfiguration();
                BackendCfgClient userRoot = config.getRootConfiguration().getBackend("userRoot");
            try (ManagementContext managementContext = server.getConfiguration()) {
                BackendCfgClient userRoot = managementContext.getRootConfiguration().getBackend("userRoot");
                userRoot.setBaseDN((Collections.singletonList(baseDN)));
                userRoot.setEnabled(true);
                userRoot.commit();
                config.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
@@ -163,18 +200,167 @@
        }
    }
    /**
     * Stops this instance, if it is still running, and deletes its temporary directory.
     * <p>
     * This method is idempotent: it is also registered as a JVM shutdown hook.
     * <p>
     * When the server cannot be stopped, this instance stays open and keeps its shutdown hook
     * registered, so that a later call - or the hook at JVM exit - retries the stop.
     */
    @Override
    public void close()  {
    public synchronized void close()  {
        if (closed) {
            return;
        }
        final boolean fromShutdownHook = Thread.currentThread() == shutdownHook;
        if (server.isRunning()) {
            try {
                logger.info("Shutting down OpenDJ ...");
                server.stop(this.getClass().getName(), LocalizableMessage.raw("Stopped after receiving Control-C"));
            }catch (Throwable e) {
                logger.error("Error stopping OpenDJ", e);
                if (!fromShutdownHook) {
                    // The server is still running: deleting its directory now would destroy a
                    // live installation. Leave this instance open, with its shutdown hook still
                    // registered, so that the stop can be retried.
                    return;
                }
                // The JVM is going down and there will be no later attempt, so the temporary
                // directory is removed even though the server has not stopped cleanly.
            }
        }
        // close() is also registered as a shutdown hook, so deletion must stay idempotent
        FileUtils.deleteQuietly(instanceDirectory);
        closed = true;
        unregisterShutdownHook();
        deleteInstanceDirectory(instanceDirectory,
                fromShutdownHook ? shortDeleteTimeout(config) : config.getDeleteTimeout());
    }
    private void checkNotClosed() {
        if (closed) {
            throw new IllegalStateException("this embedded OpenDJ instance is closed");
        }
    }
    private static long shortDeleteTimeout(Config config) {
        return Math.min(config.getDeleteTimeout(), SHORT_DELETE_TIMEOUT_MS);
    }
    private void unregisterShutdownHook() {
        try {
            Runtime.getRuntime().removeShutdownHook(shutdownHook);
        } catch (IllegalStateException e) {
            // close() was reached from the shutdown hook itself: nothing to unregister
        }
    }
    /**
     * Deletes the temporary directory of an instance, retrying for a bounded period and
     * falling back to a deletion on JVM exit.
     * <p>
     * Deleting once is not enough. On Windows a file cannot be deleted while a handle to it
     * is still open, and {@code server.stop()} does not wait for the server threads to
     * terminate: an embedded server runs them as daemon threads, which the shutdown monitor
     * of the directory server ignores. Some handles are therefore released shortly after
     * {@code stop()} has returned, and a single best-effort deletion loses that race and
     * silently leaks the whole directory, backend data included. Retrying is a way to wait
     * for those handles to be released - a single attempt already removes everything that is
     * not in use at that moment.
     *
     * @param directory
     *            the directory to delete, may be {@code null} when the instance failed to
     *            initialize before creating it
     * @param timeoutMs
     *            how long the deletion is retried, in milliseconds
     */
    static void deleteInstanceDirectory(File directory, long timeoutMs) {
        deleteInstanceDirectory(directory, timeoutMs, FileUtils::deleteQuietly);
    }
    /**
     * Implements {@link #deleteInstanceDirectory(File, long)} with an injectable deletion, so
     * that tests can drive the retries without depending on a genuinely undeletable file.
     *
     * @param deleteAttempt
     *            performs one deletion attempt and reports whether the directory is gone
     */
    static void deleteInstanceDirectory(File directory, long timeoutMs, Predicate<File> deleteAttempt) {
        if (directory == null || !directory.exists()) {
            return;
        }
        final long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
        long retryDelay = INITIAL_DELETE_RETRY_DELAY_MS;
        while (true) {
            // The second test covers a directory removed by someone else in the meantime:
            // deleteQuietly() reports a failure for a directory that is already gone.
            if (deleteAttempt.test(directory) || !directory.exists()) {
                return;
            }
            if (System.nanoTime() >= deadline) {
                logger.warn("Cannot delete {} within {} ms, some files are still in use. "
                        + "They are now scheduled for deletion on JVM exit:{}",
                        directory, timeoutMs, remainingPaths(directory));
                break;
            }
            try {
                Thread.sleep(retryDelay);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                logger.warn("Interrupted while deleting {}. What is left is now scheduled "
                        + "for deletion on JVM exit:{}", directory, remainingPaths(directory));
                break;
            }
            retryDelay = Math.min(retryDelay * 2, MAX_DELETE_RETRY_DELAY_MS);
        }
        deleteTreeOnExit(directory);
    }
    /**
     * Describes what is left in the given directory: one path per line, at most
     * {@link #MAX_REPORTED_REMAINING_PATHS} of them, followed by the number of paths left out.
     */
    static String remainingPaths(File directory) {
        final List<String> reported = new ArrayList<>();
        final int total = collectRemainingPaths(directory, reported);
        final StringBuilder description = new StringBuilder();
        for (String path : reported) {
            description.append("\n  ").append(path);
        }
        if (total > reported.size()) {
            description.append("\n  ... and ").append(total - reported.size()).append(" more");
        }
        return description.toString();
    }
    /**
     * Adds at most {@link #MAX_REPORTED_REMAINING_PATHS} paths of the given tree to
     * {@code reported} and returns how many paths it holds in total.
     */
    private static int collectRemainingPaths(File file, List<String> reported) {
        final File[] children = file.listFiles();
        if (children == null || children.length == 0) {
            if (reported.size() < MAX_REPORTED_REMAINING_PATHS) {
                reported.add(file.getPath());
            }
            return 1;
        }
        int total = 0;
        for (File child : children) {
            total += collectRemainingPaths(child, reported);
        }
        return total;
    }
    private static void deleteTreeOnExit(File file) {
        // File.deleteOnExit() deletes in reverse order of registration, so a directory has to
        // be registered before its content for the content to be removed first.
        file.deleteOnExit();
        final File[] children = file.listFiles();
        if (children != null) {
            for (File child : children) {
                deleteTreeOnExit(child);
            }
        }
    }
    private void copyFilesFromJar(List<String> jarFiles, String jarDirectory, File outputDirectory) throws IOException{
@@ -183,65 +369,80 @@
            final String resourcePath = !jarFile.contains("/")
                    ? "/"+jarDirectory + jarFile
                    : jarFile;
            InputStream in = new File(jarFile).exists()
            try (InputStream in = new File(jarFile).exists()
                    ? Files.newInputStream(new File(jarFile).toPath())
                    : MemoryBackend.class.getResourceAsStream(resourcePath);
            if (in == null) {
                throw new IOException("cannot find " + resourcePath);
                    : MemoryBackend.class.getResourceAsStream(resourcePath)) {
                if (in == null) {
                    throw new IOException("cannot find " + resourcePath);
                }
                FileUtils.copyInputStreamToFile(in, outputFile);
            }
            FileUtils.copyInputStreamToFile(in, outputFile);
            in.close();
        }
    }
    /**
     * Imports the LDIF read from the given stream.
     * <p>
     * The stream is closed by this method, whether the import succeeds or not.
     *
     * @param inputStream
     *            the LDIF to import
     * @throws IllegalStateException
     *             If this instance has been closed.
     */
    public void importData(InputStream inputStream) throws EmbeddedDirectoryServerException, IOException {
        checkNotClosed();
        logger.info("start import ldif from stream");
        EntryReader reader;
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            reader = new LDIFEntryReader(bufferedReader);
        } catch (Exception e) {
            logger.error("import ldif : {}", e, e);
            throw e;
        }
        org.forgerock.opendj.ldap.Entry  entryBefore;
        final Connection connection = server.getInternalConnection();
        long recordCount = 0;
        while (reader.hasNext() && (entryBefore = reader.readEntry()) != null) {
            recordCount++;
            try {
                connection.add(entryBefore);
                logger.info("import ldif : {}",entryBefore.getName());
            }catch (LdapException e) {
                logger.error("import ldif : {} {}",entryBefore.getName(),e.toString());
        try (EntryReader reader = new LDIFEntryReader(new BufferedReader(new InputStreamReader(inputStream)));
             Connection connection = server.getInternalConnection()) {
            while (reader.hasNext() && (entryBefore = reader.readEntry()) != null) {
                recordCount++;
                try {
                    connection.add(entryBefore);
                    logger.info("import ldif : {}",entryBefore.getName());
                }catch (LdapException e) {
                    logger.error("import ldif : {} {}",entryBefore.getName(),e.toString());
                }
            }
        }
        if(recordCount == 0) {
            logger.error("no records were imported, check file contents and permissions");
            throw new RuntimeException("no records were imported");
        }
        reader.close();
        connection.close();
    }
    /**
     * Writes the entries below the given base DN to the given stream, as LDIF.
     * <p>
     * The stream is flushed and closed by this method, whether the export succeeds or not.
     *
     * @param baseDN
     *            the base DN of the subtree to export
     * @param out
     *            where the LDIF is written
     * @throws IllegalStateException
     *             If this instance has been closed.
     */
    public void getData(String baseDN, OutputStream out) throws IOException, EmbeddedDirectoryServerException {
        LDIFEntryWriter ldifWriter = new LDIFEntryWriter(out);
        final Connection connection = server.getInternalConnection();
        ConnectionEntryReader reader = connection.search(baseDN, SearchScope.WHOLE_SUBTREE, "(objectClass=*)");
        while(reader.hasNext()) {
            if (!reader.isReference()) {
                SearchResultEntry se = reader.readEntry();
                if (!skipEntry(se)) {
                    ldifWriter.writeEntry(se);
                    logger.info("export {}", se.toString());
        checkNotClosed();
        // resources are closed in reverse order, so the writer is flushed and closed last
        try (LDIFEntryWriter ldifWriter = new LDIFEntryWriter(out);
             Connection connection = server.getInternalConnection();
             ConnectionEntryReader reader =
                     connection.search(baseDN, SearchScope.WHOLE_SUBTREE, "(objectClass=*)")) {
            while (reader.hasNext()) {
                if (!reader.isReference()) {
                    SearchResultEntry se = reader.readEntry();
                    if (!skipEntry(se)) {
                        ldifWriter.writeEntry(se);
                        logger.info("export {}", se.toString());
                    }
                }
            }
        }
        reader.close();
        ldifWriter.close();
        connection.close();
    }
    private boolean skipEntry(SearchResultEntry se) {
opendj-embedded/src/test/java/org/openidentityplatform/opendj/embedded/ConfigTest.java
New file
@@ -0,0 +1,124 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2026 3A Systems LLC.
 */
package org.openidentityplatform.opendj.embedded;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.util.ArrayList;
import java.util.List;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
/**
 * Tests how the numeric configuration properties are read: they are parsed while the instance
 * is being created, so a value that cannot be parsed has to name the property that carried it
 * instead of surfacing as a bare {@link NumberFormatException} from a constructor.
 */
public class ConfigTest {
    private static final String PREFIX = Config.class.getPackage().getName();
    private final List<String> propertiesSet = new ArrayList<>();
    @AfterMethod
    public void clearProperties() {
        for (String name : propertiesSet) {
            System.clearProperty(name);
        }
        propertiesSet.clear();
    }
    @DataProvider
    public Object[][] numericProperties() {
        return new Object[][] { { ".port" }, { ".admin_port" }, { ".jmx_port" }, { ".delete_timeout" } };
    }
    @Test
    public void readsTheDefaultsWhenNoPropertyIsSet() {
        final Config config = new Config();
        assertEquals(config.getPort(), 1389);
        assertEquals(config.getAdminPort(), 4444);
        assertEquals(config.getJmxPort(), 1689);
        assertEquals(config.getDeleteTimeout(), 10_000L);
    }
    @Test
    public void readsTheNumbersFromTheSystemProperties() {
        setProperty(".port", "11389");
        setProperty(".admin_port", "14444");
        setProperty(".jmx_port", "11689");
        setProperty(".delete_timeout", "0");
        final Config config = new Config();
        assertEquals(config.getPort(), 11389);
        assertEquals(config.getAdminPort(), 14444);
        assertEquals(config.getJmxPort(), 11689);
        assertEquals(config.getDeleteTimeout(), 0L);
    }
    @Test(dataProvider = "numericProperties")
    public void namesThePropertyWhoseValueIsNotANumber(String property) {
        setProperty(property, "not a number");
        try {
            new Config();
            fail("a value that is not a number should have been rejected");
        } catch (IllegalArgumentException e) {
            assertTrue(e.getMessage().contains(PREFIX + property), e.getMessage());
            assertTrue(e.getMessage().contains("not a number"), e.getMessage());
            assertTrue(e.getCause() instanceof NumberFormatException, "unexpected cause: " + e.getCause());
        }
    }
    @Test
    public void rejectsANegativeDeleteTimeoutProperty() {
        setProperty(".delete_timeout", "-1");
        try {
            new Config();
            fail("a negative timeout should have been rejected");
        } catch (IllegalArgumentException e) {
            assertTrue(e.getMessage().contains(PREFIX + ".delete_timeout"), e.getMessage());
            assertTrue(e.getMessage().contains("negative"), e.getMessage());
        }
    }
    @Test
    public void rejectsANegativeDeleteTimeout() {
        final Config config = new Config();
        try {
            config.setDeleteTimeout(-1);
            fail("a negative timeout should have been rejected");
        } catch (IllegalArgumentException e) {
            assertTrue(e.getMessage().contains("-1"), e.getMessage());
        }
        assertEquals(config.getDeleteTimeout(), 10_000L, "the timeout has been changed");
    }
    private void setProperty(String property, String value) {
        final String name = PREFIX + property;
        System.setProperty(name, value);
        propertiesSet.add(name);
    }
}
opendj-embedded/src/test/java/org/openidentityplatform/opendj/embedded/EmbeddedOpenDJCleanupTest.java
New file
@@ -0,0 +1,181 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2026 3A Systems LLC.
 */
package org.openidentityplatform.opendj.embedded;
import org.apache.commons.io.FileUtils;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
/**
 * Tests the deletion of the temporary directory of an embedded instance, which has to survive
 * the files that the server threads are still holding when {@code stop()} returns.
 * <p>
 * The deletion itself is injected, so that the retries can be driven without depending on a
 * genuinely undeletable file, which no platform offers in a portable way.
 */
public class EmbeddedOpenDJCleanupTest {
    private final List<File> temporaryDirectories = new ArrayList<>();
    @AfterMethod
    public void deleteTemporaryDirectories() {
        for (File directory : temporaryDirectories) {
            FileUtils.deleteQuietly(directory);
        }
        temporaryDirectories.clear();
    }
    @Test
    public void deletesTheWholeTree() throws Exception {
        final File directory = createTree(3);
        EmbeddedOpenDJ.deleteInstanceDirectory(directory, 0);
        assertFalse(directory.exists(), directory + " has not been deleted");
    }
    @Test
    public void ignoresADirectoryThatDoesNotExist() throws Exception {
        final File directory = createTree(0);
        assertTrue(FileUtils.deleteQuietly(directory));
        //neither of these must throw
        EmbeddedOpenDJ.deleteInstanceDirectory(directory, 10_000);
        EmbeddedOpenDJ.deleteInstanceDirectory(null, 10_000);
    }
    @Test
    public void stopsRetryingAsSoonAsTheDeletionSucceeds() throws Exception {
        final File directory = createTree(1);
        final AtomicInteger attempts = new AtomicInteger();
        final long elapsedMs = timed(() ->
                EmbeddedOpenDJ.deleteInstanceDirectory(directory, 10_000, file -> attempts.incrementAndGet() >= 3));
        assertEquals(attempts.get(), 3, "the deletion should have been retried until it succeeded");
        assertTrue(elapsedMs < 10_000, "it waited " + elapsedMs + " ms instead of returning on success");
    }
    @Test
    public void triesOnlyOnceWithoutATimeout() throws Exception {
        final File directory = createTree(1);
        final AtomicInteger attempts = new AtomicInteger();
        EmbeddedOpenDJ.deleteInstanceDirectory(directory, 0, file -> {
            attempts.incrementAndGet();
            return false;
        });
        assertEquals(attempts.get(), 1, "a zero timeout must not retry");
        assertTrue(directory.exists(), "the injected deletion did not delete anything");
    }
    @Test
    public void givesUpWhenTheTimeoutExpires() throws Exception {
        final File directory = createTree(1);
        final AtomicInteger attempts = new AtomicInteger();
        final long timeoutMs = 300;
        final long elapsedMs = timed(() -> EmbeddedOpenDJ.deleteInstanceDirectory(directory, timeoutMs, file -> {
            attempts.incrementAndGet();
            return false;
        }));
        assertTrue(attempts.get() > 1, "the deletion should have been retried, attempts: " + attempts.get());
        assertTrue(elapsedMs >= timeoutMs, "it gave up after " + elapsedMs + " ms, before the timeout");
        assertTrue(elapsedMs < TimeUnit.SECONDS.toMillis(30), "it did not give up, " + elapsedMs + " ms elapsed");
        //the leftovers are registered for deletion on JVM exit, so this must return normally
        assertTrue(directory.exists());
    }
    @Test
    public void givesUpWhenTheThreadIsInterrupted() throws Exception {
        final File directory = createTree(1);
        final AtomicInteger attempts = new AtomicInteger();
        final long elapsedMs;
        Thread.currentThread().interrupt();
        try {
            elapsedMs = timed(() -> EmbeddedOpenDJ.deleteInstanceDirectory(directory, 60_000, file -> {
                attempts.incrementAndGet();
                return false;
            }));
            assertTrue(Thread.currentThread().isInterrupted(), "the interrupted status has been swallowed");
        } finally {
            Thread.interrupted();
        }
        assertEquals(attempts.get(), 1, "an interrupted deletion must not be retried");
        assertTrue(elapsedMs < TimeUnit.SECONDS.toMillis(30), "it kept retrying for " + elapsedMs + " ms");
    }
    @Test
    public void reportsTheRemainingPathsAndHowManyAreLeftOut() throws Exception {
        final File directory = createTree(25);
        final String remaining = EmbeddedOpenDJ.remainingPaths(directory);
        final String[] lines = remaining.split("\n");
        //one empty leading token before the first line separator, 20 paths and the summary
        assertEquals(lines.length, 22, "unexpected report: " + remaining);
        for (int i = 1; i < lines.length - 1; i++) {
            assertTrue(lines[i].startsWith("  " + directory.getPath()), "unexpected line: " + lines[i]);
        }
        assertTrue(remaining.endsWith("... and 5 more"), remaining);
    }
    @Test
    public void reportsAllTheRemainingPathsWhenThereAreFewOfThem() throws Exception {
        final File directory = createTree(2);
        final String remaining = EmbeddedOpenDJ.remainingPaths(directory);
        assertEquals(remaining.split("\n").length, 3, "unexpected report: " + remaining);
        assertFalse(remaining.contains("more"), remaining);
    }
    /** Creates a temporary directory holding the given number of files, one of them nested. */
    private File createTree(int fileCount) throws IOException {
        final File directory = Files.createTempDirectory("opendj-cleanup-test").toFile();
        temporaryDirectories.add(directory);
        final File subDirectory = new File(directory, "nested");
        assertTrue(subDirectory.mkdir());
        for (int i = 0; i < fileCount; i++) {
            final File parent = i == 0 ? subDirectory : directory;
            Files.write(new File(parent, "file" + i + ".txt").toPath(), new byte[] { 'x' });
        }
        return directory;
    }
    private static long timed(Runnable runnable) {
        final long start = System.nanoTime();
        runnable.run();
        return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    }
}
opendj-embedded/src/test/java/org/openidentityplatform/opendj/embedded/EmbeddedOpenDJTest.java
@@ -26,16 +26,19 @@
import org.forgerock.opendj.ldif.ConnectionEntryReader;
import org.testng.annotations.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
public class EmbeddedOpenDJTest {
@@ -51,44 +54,71 @@
        //start embedded OpenDJ server
        EmbeddedOpenDJ embeddedOpenDJ = new EmbeddedOpenDJ(config);
        embeddedOpenDJ.run();
        assertTrue(embeddedOpenDJ.isRunning());
        File serverRoot = embeddedOpenDJ.getServerRootDirectory();
        assertTrue(serverRoot.isDirectory());
        try {
            embeddedOpenDJ.run();
            assertTrue(embeddedOpenDJ.isRunning());
            assertTrue(serverRoot.isDirectory());
        //import ldif data from an input stream
        URI resUri = getClass().getClassLoader().getResource("opendj/data.ldif").toURI();
        byte[] bytes = Files.readAllBytes(Paths.get(resUri));
        String newBytes = new String(bytes);
        InputStream is = new ByteArrayInputStream(newBytes.getBytes(StandardCharsets.UTF_8));
        embeddedOpenDJ.importData(is);
            //import ldif data from an input stream
            URI resUri = getClass().getClassLoader().getResource("opendj/data.ldif").toURI();
            try (InputStream is = Files.newInputStream(Paths.get(resUri))) {
                embeddedOpenDJ.importData(is);
            }
        //export OpenDJ data
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        embeddedOpenDJ.getData("dc=openidentityplatform,dc=org", bos);
        String imported = bos.toString();
        assertTrue(imported.contains("dn: uid=jdoe,ou=people,dc=openidentityplatform,dc=org"));
            //export OpenDJ data
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            embeddedOpenDJ.getData(config.getBaseDN(), bos);
            //getData() writes LDIF in the platform default charset, so it has to be read back
            //in the same one: LDIFEntryWriter(OutputStream) wraps it in a plain OutputStreamWriter
            String imported = bos.toString();
            assertTrue(imported.contains("dn: uid=jdoe,ou=people," + config.getBaseDN()));
        //test search in the imported data
        try(LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", 1389);
            Connection connection = factory.getConnection()) {
            BindResult result = connection.bind("cn=Directory Manager", "passw0rd".toCharArray());
            assertTrue(result.isSuccess());
            //test search in the imported data
            try (LDAPConnectionFactory factory = new LDAPConnectionFactory("localhost", config.getPort());
                 Connection connection = factory.getConnection()) {
                BindResult result = connection.bind("cn=Directory Manager", config.getAdminPassword().toCharArray());
                assertTrue(result.isSuccess());
            SearchRequest request = Requests.newSearchRequest("dc=openidentityplatform,dc=org",
                    SearchScope.WHOLE_SUBTREE, "(uid=jdoe)", "uid");
            ConnectionEntryReader reader = connection.search(request);
            SearchResultEntry entry = reader.readEntry();
            entry.getAllAttributes();
                SearchRequest request = Requests.newSearchRequest(config.getBaseDN(),
                        SearchScope.WHOLE_SUBTREE, "(uid=jdoe)", "uid");
                try (ConnectionEntryReader reader = connection.search(request)) {
                    SearchResultEntry entry = reader.readEntry();
                    assertEquals(entry.getName().toString(), "uid=jdoe,ou=people," + config.getBaseDN());
                    assertEquals(entry.parseAttribute("uid").asString(), "jdoe");
                    assertFalse(reader.hasNext(), "the search returned more than one entry");
                }
            }
        } finally {
            //stop OpenDJ
            embeddedOpenDJ.close();
        }
        //stop OpenDJ
        embeddedOpenDJ.close();
        assertFalse(embeddedOpenDJ.isRunning());
        //the per-instance temporary directory is deleted on close
        assertFalse(serverRoot.exists());
        assertFalse(serverRoot.getParentFile().exists());
        assertFalse(serverRoot.exists(), leftovers(serverRoot));
        assertFalse(serverRoot.getParentFile().exists(), leftovers(serverRoot.getParentFile()));
        //a closed instance cannot be used any more: its directory is gone
        assertThrows(IllegalStateException.class, embeddedOpenDJ::run);
        assertThrows(IllegalStateException.class, embeddedOpenDJ::getServerRootDirectory);
        assertThrows(IllegalStateException.class, () -> embeddedOpenDJ.getData(config.getBaseDN(), new ByteArrayOutputStream()));
        //close() is idempotent, it is also registered as a shutdown hook
        embeddedOpenDJ.close();
    }
    /** Describes what is left in the given directory, to make an assertion failure diagnosable. */
    private static String leftovers(File directory) {
        if (!directory.exists()) {
            return "";
        }
        final StringBuilder message = new StringBuilder(directory + " still exists and contains:");
        try (Stream<Path> paths = Files.walk(directory.toPath())) {
            paths.forEach(path -> message.append("\n  ").append(path));
        } catch (IOException e) {
            message.append(" <cannot be listed: ").append(e).append('>');
        }
        return message.toString();
    }
}
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/ModifyAsync.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2015 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.examples;
@@ -78,20 +79,24 @@
        final String userName = args[2];
        final char[] password = args[3].toCharArray();
        // Create the LDIF reader using either the named file, if provided, or stdin.
        InputStream ldif;
        // Read the LDIF from either the named file, if provided, or stdin. Only a file is closed
        // here; stdin belongs to the caller.
        final String[] ldifLines;
        if (args.length > 4) {
            try {
                ldif = new FileInputStream(args[4]);
            try (InputStream ldif = new FileInputStream(args[4])) {
                ldifLines = getInputLines(ldif);
            } catch (final FileNotFoundException e) {
                System.err.println(e.getMessage());
                System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
                return;
            } catch (final IOException e) {
                System.err.println(e.getMessage());
                System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
                return;
            }
        } else {
            ldif = System.in;
            ldifLines = getInputLines(System.in);
        }
        final String[] ldifLines = getInputLines(ldif);
        // Connect to the server, bind, and request the modifications.
        new LDAPConnectionFactory(hostName, port)
opendj-ldap-sdk-examples/src/main/java/org/forgerock/opendj/examples/Server.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2009-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.examples;
@@ -80,8 +81,9 @@
        // Create the memory backend.
        final MemoryBackend backend;
        try {
            backend = new MemoryBackend(new LDIFEntryReader(new FileInputStream(ldifFileName)));
        try (FileInputStream ldifStream = new FileInputStream(ldifFileName);
                LDIFEntryReader ldifReader = new LDIFEntryReader(ldifStream)) {
            backend = new MemoryBackend(ldifReader);
        } catch (final IOException e) {
            System.err.println(e.getMessage());
            System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Base64.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package com.forgerock.opendj.ldap.tools;
@@ -42,6 +43,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
@@ -246,11 +248,14 @@
            dataToDecode = encodedDataArg.getValue();
        } else {
            final boolean readFromFile = encodedDataFilePathArg.isPresent();
            Reader source = null;
            BufferedReader reader = null;
            final String encodedDataFilePath = encodedDataFilePathArg.getValue();
            try {
                reader = new BufferedReader(readFromFile ? new FileReader(encodedDataFilePath)
                                                         : new InputStreamReader(System.in));
                // Only a file is closed below; System.in belongs to the caller.
                source = readFromFile ? new FileReader(encodedDataFilePath)
                                      : new InputStreamReader(System.in);
                reader = new BufferedReader(source);
                final StringBuilder buffer = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
@@ -270,7 +275,7 @@
                                       ERR_BASE64_CANNOT_READ_ENCODED_DATA.get(getExceptionMessage(e)));
            } finally {
                if (readFromFile) {
                    closeSilently(reader);
                    closeSilently(reader, source);
                }
            }
        }
opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/Utils.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2026 3A Systems, LLC.
 * Portions copyright 2014-2016 ForgeRock AS.
 * Portions copyright 2022-2024 3A Systems,LLC.
 */
@@ -182,6 +183,9 @@
        case CHANGE_AFTER_RESET:
            app.println(INFO_BIND_MUST_CHANGE_PASSWORD.get());
            break;
        default:
          // No action needed for the remaining values.
          break;
        }
    }
opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateConfigMojo.java
@@ -184,7 +184,7 @@
        } else if (!isXMLPackageDirectoryValid()) {
            throw new MojoExecutionException("The XML definition directory \""
                    + getXMLPackageDirectory() + "\" does not exist.");
        } else if (getClass().getResource(getStylesheetDirectory()) == null) {
        } else if (GenerateConfigMojo.class.getResource(getStylesheetDirectory()) == null) {
            throw new MojoExecutionException("The XSLT stylesheet directory \""
                    + getStylesheetDirectory() + "\" does not exist.");
        }
@@ -439,7 +439,7 @@
    private Templates loadStylesheet(final String stylesheet)
            throws TransformerConfigurationException {
        final Source xslt =
                new StreamSource(getClass().getResourceAsStream(
                new StreamSource(GenerateConfigMojo.class.getResourceAsStream(
                        getStylesheetDirectory() + "/" + stylesheet));
        return stylesheetFactory.newTemplates(xslt);
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2017 Rosie Applications, Inc.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.rest2ldap;
@@ -176,10 +177,10 @@
    public Rest2LdapHttpApplication() {
        try {
            // The null check is required for unit test mocks because the resource does not exist.
            final URL configUrl = getClass().getResource("/config.json");
            final URL configUrl = Rest2LdapHttpApplication.class.getResource("/config.json");
            this.configDirectory = configUrl != null ? new File(configUrl.toURI()).getParentFile() : null;
        } catch (final URISyntaxException e) {
            throw new IllegalStateException(""+getClass().getResource("/config.json"),e);
            throw new IllegalStateException("" + Rest2LdapHttpApplication.class.getResource("/config.json"), e);
        }
        this.schema = Schema.getDefaultSchema();
    }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/schema/JsonQueryEqualityMatchingRuleImpl.java
@@ -12,6 +12,7 @@
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.rest2ldap.schema;
@@ -430,7 +431,7 @@
        if (value == null) {
            return createNullIndexKey(builder);
        } else if (value instanceof Number) {
            final Double doubleValue = ((Number) value).doubleValue();
            final double doubleValue = ((Number) value).doubleValue();
            return createNumberIndexKey(builder, BigDecimal.valueOf(doubleValue));
        } else if (value instanceof Boolean) {
            final Boolean booleanValue = (Boolean) value;
opendj-server-legacy/replace.rb
@@ -1,4 +1,7 @@
#!/usr/bin/env ruby
#
# Portions Copyright 2026 3A Systems, LLC.
#
require 'fileutils'
@@ -471,7 +474,6 @@
      (0..replacements.size-1).step(2).each { |index|
        pattern, replace = replacements[index], replacements[index+1]
        replace = replace.gsub('{CLASSNAME}', classname(file))
        is_replaced = true
        #while is_replaced
          #puts "pattern: " + pattern.to_s
          is_replaced = contents.gsub!(pattern, replace)
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.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.forgerock.opendj.reactive;
@@ -189,6 +190,13 @@
     */
    private final Object waitListen = new Object();
    /**
     * Condition predicate for {@link #waitListen}: set once the handler thread has attempted to open the listen socket
     * (successfully or not). Guarded by the {@link #waitListen} monitor; protects the start method against spurious
     * wakeups.
     */
    private boolean listenAttempted;
    /** The friendly name of this connection handler. */
    private String friendlyName;
@@ -679,6 +687,38 @@
    }
    /**
     * {@inheritDoc}
     * <p>
     * Deliberately left unsynchronized, unlike the overridden {@link Thread#start()}: the state this method touches is
     * guarded by the {@link #waitListen} monitor, and holding the thread's own monitor across {@code waitListen.wait()}
     * would block {@link Thread#join()}.
     */
    @Override
    public void start() {
        // The Directory Server start process should only return when the connection handler port is fully opened
        // and working. The start method therefore needs to wait for the created thread.
        synchronized (waitListen) {
            super.start();
            final long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(LISTEN_TIMEOUT_MS);
            try {
                while (!listenAttempted) {
                    final long remainingMs = TimeUnit.NANOSECONDS.toMillis(deadlineNanos - System.nanoTime());
                    if (remainingMs <= 0) {
                        logger.error(ERR_CONNHANDLER_TIMEOUT_WAITING_FOR_LISTENER, handlerName,
                                TimeUnit.MILLISECONDS.toSeconds(LISTEN_TIMEOUT_MS));
                        break;
                    }
                    waitListen.wait(remainingMs);
                }
            } catch (InterruptedException e) {
                // If something interrupted the start its probably better to return ASAP.
                Thread.currentThread().interrupt();
            }
        }
    }
    /**
     * Operates in a loop, accepting new connections and ensuring that requests on those connections are handled
     * properly.
     */
@@ -701,7 +741,8 @@
                    // so notify here to allow the server startup to complete.
                    synchronized (waitListen) {
                        starting = false;
                        waitListen.notify();
                        listenAttempted = true;
                        waitListen.notifyAll();
                    }
                }
@@ -716,12 +757,6 @@
            }
            try {
                // At this point, the connection Handler either started correctly or failed
                // to start but the start process should be notified and resume its work in any cases.
                synchronized (waitListen) {
                    waitListen.notify();
                }
                // If we have gotten here, then we are about to start listening
                // for the first time since startup or since we were previously disabled.
                startListener();
@@ -749,6 +784,13 @@
                } else {
                    lastIterationFailed = true;
                }
            } finally {
                // At this point, the connection handler either started listening or failed to do so,
                // but the start process should be notified and resume its work in any cases.
                synchronized (waitListen) {
                    listenAttempted = true;
                    waitListen.notifyAll();
                }
            }
        }
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/browser/BrowserController.java
@@ -152,7 +152,7 @@
    connectionPool = cpool;
    connectionPool.addReferralAuthenticationListener(this);
    refreshQueue = new NodeSearcherQueue("New red", 2);
    refreshQueue = new NodeSearcherQueue("New red", 2).start();
    // NUMSUBORDINATE HACK
    // Create an empty hacker to avoid null value test.
@@ -1827,6 +1827,9 @@
          errorType = ERROR_SEARCHING_CHILDREN;
          break;
        default:
          // No action needed for the remaining values.
          break;
        }
        errorException = error.getException();
        errorArg = error.getArg();
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/browser/NodeSearcherQueue.java
@@ -39,6 +39,7 @@
  private final List<AbstractNodeTask> waitingQueue = new ArrayList<>();
  private final Map<BasicNode, AbstractNodeTask> workingList = new HashMap<>();
  private final ThreadGroup threadGroup;
  private final List<Thread> threads = new ArrayList<>();
  /**
@@ -53,8 +54,23 @@
    for (int i = 0; i < threadCount; i++) {
      Thread t = new Thread(threadGroup, this, name + "[" + i + "]");
      t.setPriority(Thread.MIN_PRIORITY);
      threads.add(t);
    }
  }
  /**
   * Starts the threads consuming this queue.
   * <p>
   * The threads are not started by the constructor so that {@code this} is not published to them
   * before construction has completed.
   *
   * @return this queue
   */
  public NodeSearcherQueue start() {
    for (Thread t : threads) {
      t.start();
    }
    return this;
  }
  /**
@@ -83,7 +99,7 @@
      throw new IllegalArgumentException("null argument");
    }
    waitingQueue.add(nodeTask);
    notify();
    notifyAll();
//    System.out.println("Queued " + nodeTask + " in " + _name);
  }
@@ -113,7 +129,7 @@
    if (task != null) {
      task.cancel();
    }
    notify();
    notifyAll();
  }
  /**
@@ -197,7 +213,7 @@
      throw new IllegalArgumentException("null argument");
    }
    workingList.remove(task.getNode());
    notify();
    notifyAll();
//    System.out.println("Flushed " + task + " from " + _name);
  }
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/task/Task.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2009 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.task;
@@ -817,6 +818,9 @@
      case DELETE:
        sb.append("delete: ").append(attrDesc).append("<br>");
        break;
      default:
        // No action needed for the remaining values.
        break;
      }
      for (ByteString value : attr)
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/BrowseEntriesPanel.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.ui;
@@ -628,7 +629,7 @@
    }
    synchronized (entryReaderThread)
    {
      entryReaderThread.notify();
      entryReaderThread.notifyAll();
    }
  }
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/GenericDialog.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2009 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.ui;
@@ -200,10 +201,6 @@
    }
    if (visible && lastComponentWithFocus != null && lastComponentWithFocus.isVisible())
    {
      if (lastComponentWithFocus == null)
      {
        lastComponentWithFocus = panel.getPreferredFocusComponent();
      }
      lastComponentWithFocus.requestFocusInWindow();
    }
    updateDefaultButton(panel);
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/IndexPanel.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2009 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.ui;
@@ -404,6 +405,9 @@
      case SUBSTRING:
        substring.setSelected(true);
        break;
      default:
        // No action needed for the remaining values.
        break;
      }
    }
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/StatusGenericPanel.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.ui;
@@ -2135,6 +2136,9 @@
      args.add("--recurringTask");
      args.add(schedule.getCronValue());
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
    return args;
  }
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/components/CustomTree.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.ui.components;
@@ -278,7 +279,7 @@
  }
  @Override
  public void addMouseListener(MouseListener mouseListener)
  public synchronized void addMouseListener(MouseListener mouseListener)
  {
    super.addMouseListener(mouseListener);
    if (mouseListeners == null)
@@ -289,7 +290,7 @@
  }
  @Override
  public void removeMouseListener(MouseListener mouseListener)
  public synchronized void removeMouseListener(MouseListener mouseListener)
  {
    super.removeMouseListener(mouseListener);
    mouseListeners.remove(mouseListener);
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/components/FilterTextField.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.controlpanel.ui.components;
@@ -131,13 +132,13 @@
  }
  @Override
  public void addActionListener(ActionListener listener)
  public synchronized void addActionListener(ActionListener listener)
  {
    listeners.add(listener);
  }
  @Override
  public void removeActionListener(ActionListener listener)
  public synchronized void removeActionListener(ActionListener listener)
  {
    listeners.remove(listener);
  }
opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/util/Utilities.java
@@ -2412,7 +2412,7 @@
      {
        return NO_VALUE_SET.toString();
      }
      Long l = Long.parseLong(monitoringValue);
      long l = Long.parseLong(monitoringValue);
      Date date = new Date(l);
      return ConfigFromConnection.newDateFormatter().format(date);
    }
@@ -2438,7 +2438,7 @@
    }
    else if (attr.isValueInBytes())
    {
      Long l = Long.parseLong(monitoringValue);
      long l = Long.parseLong(monitoringValue);
      long mb = l / (1024 * 1024);
      long kbs = (l - mb * 1024 * 1024) / 1024;
      return INFO_CTRL_PANEL_MEMORY_VALUE.get(mb, kbs).toString();
opendj-server-legacy/src/main/java/org/opends/guitools/uninstaller/Uninstaller.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.guitools.uninstaller;
@@ -1578,6 +1579,10 @@
            stopProcessing = true;
          }
        }
        break;
      default:
        // No action needed for the remaining values.
        break;
      }
      exceptionMsgs.add(getMessage(e));
    }
opendj-server-legacy/src/main/java/org/opends/quicksetup/BuildInformation.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.quicksetup;
@@ -61,6 +62,7 @@
    ProcessBuilder pb = new ProcessBuilder(args);
    InputStream is = null;
    OutputStream out = null;
    BufferedReader reader = null;
    final boolean[] done = {false};
    try {
      Map<String, String> env = pb.environment();
@@ -100,7 +102,7 @@
        });
        t.start();
      }
      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
      reader = new BufferedReader(new InputStreamReader(is));
      String line = reader.readLine();
      bi.values.put(NAME, line);
      StringBuilder sb = new StringBuilder();
@@ -154,7 +156,7 @@
    } finally {
      done[0] = true;
      StaticUtils.close(is, out);
      StaticUtils.close(reader, is, out);
    }
    // Make sure we got values for important properties that are used
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/Installer.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.quicksetup.installer;
@@ -550,6 +551,9 @@
      case IMPORT_AUTOMATICALLY_GENERATED_DATA:
        steps.add(InstallProgressStep.IMPORTING_AUTOMATICALLY_GENERATED);
        break;
      default:
        // No action needed for the remaining values.
        break;
      }
    }
@@ -591,7 +595,7 @@
    int cumulatedTime = 0;
    for (InstallProgressStep s : steps)
    {
      Integer statusTime = s.getRelativeDuration();
      int statusTime = s.getRelativeDuration();
      hmRatio.put(s, (100 * cumulatedTime) / totalTime);
      cumulatedTime += statusTime;
    }
@@ -928,6 +932,9 @@
        return new ProgressPanel(this);
      case FINISHED:
        return new FinishedPanel(this);
      default:
        // No action needed for the remaining values.
        break;
      }
    }
    return null;
@@ -2676,7 +2683,7 @@
            {
              throw new ApplicationException(ReturnCode.APPLICATION_ERROR, pnfe.getMessageObject(), null);
            }
            StaticUtils.sleep((5 - nTries) * 3000);
            StaticUtils.sleep((5 - nTries) * 3000L);
          }
          nTries--;
        }
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/InstallerHelper.java
@@ -33,7 +33,6 @@
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
@@ -152,8 +151,7 @@
    try
    {
      process = processBuilder.start();
      final BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
      new OutputReader(err)
      new OutputReader(process.getErrorStream())
      {
        @Override
        public void processLine(final String line)
@@ -162,10 +160,9 @@
          application.notifyListeners(LocalizableMessage.raw(line));
          application.notifyListeners(application.getLineBreak());
        }
      };
      }.start();
      final BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()));
      new OutputReader(out)
      new OutputReader(process.getInputStream())
      {
        @Override
        public void processLine(final String line)
@@ -174,7 +171,7 @@
          application.notifyListeners(LocalizableMessage.raw(line));
          application.notifyListeners(application.getLineBreak());
        }
      };
      }.start();
      return process.waitFor();
    }
@@ -182,8 +179,10 @@
    {
      if (process != null)
      {
        closeProcessStream(process.getErrorStream(), "error");
        closeProcessStream(process.getOutputStream(), "output");
        // The error and output streams of the process are owned by the readers started above,
        // which close them once drained. Only the stream writing to the standard input of the
        // process, which is never used here, is left to close.
        closeProcessStream(process.getOutputStream(), "input");
      }
    }
  }
opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/ui/RuntimeOptionsPanel.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2010 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.quicksetup.installer.ui;
@@ -383,6 +384,9 @@
            get();
        }
        break;
      default:
        // No action needed for the remaining values.
        break;
      }
    }
    else if (repl.getType() == DataReplicationOptions.Type.IN_EXISTING_TOPOLOGY)
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/OutputReader.java
@@ -13,11 +13,14 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.quicksetup.util;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -33,18 +36,24 @@
   */
  public abstract void processLine(String line);
  private final Thread thread;
  /**
   * The protected constructor.
   * <p>
   * The stream is consumed until end of stream and then closed by this reader's thread, which is
   * only launched by {@link #start()}. Wrapping the stream is done by that thread as well, so that
   * this reader is the sole owner of every resource built on top of the stream.
   *
   * @param reader  the BufferedReader of the stop process.
   * @param stream  the output stream of the process to read.
   */
  public OutputReader(final BufferedReader reader) {
    Thread t = new Thread(new Runnable() {
  public OutputReader(final InputStream stream) {
    thread = new Thread(new Runnable() {
      @Override
      public void run() {
        try {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) {
          String line;
          while (null != (line = reader.readLine())) {
          while (null != (line = in.readLine())) {
            processLine(line);
          }
        } catch (Throwable t) {
@@ -52,6 +61,18 @@
        }
      }
    });
    t.start();
  }
  /**
   * Starts consuming the reader in a background thread.
   * <p>
   * The thread is not started by the constructor so that {@code this} does not escape before
   * construction of the subclass has completed.
   *
   * @return this reader
   */
  public OutputReader start() {
    thread.start();
    return this;
  }
}
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ServerController.java
@@ -19,6 +19,7 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
@@ -181,16 +182,9 @@
          logger.info(LocalizableMessage.raw("Launching stop command, argList: "+argList));
          Process process = pb.start();
          BufferedReader err =
            new BufferedReader(
                new InputStreamReader(process.getErrorStream()));
          BufferedReader out =
            new BufferedReader(
                new InputStreamReader(process.getInputStream()));
          /* Create these objects to resend the stop process output to the details area. */
          new StopReader(err, true);
          new StopReader(out, false);
          new StopReader(process.getErrorStream(), true);
          new StopReader(process.getInputStream(), false);
          int returnValue = process.waitFor();
@@ -388,11 +382,8 @@
    String startedId = getStartedId();
    Process process = pb.start();
    BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    BufferedReader out = new BufferedReader(new InputStreamReader(process.getInputStream()));
    StartReader errReader = new StartReader(err, startedId, true);
    StartReader outputReader = new StartReader(out, startedId, false);
    StartReader errReader = new StartReader(process.getErrorStream(), startedId, true);
    StartReader outputReader = new StartReader(process.getInputStream(), startedId, false);
    int returnValue = process.waitFor();
@@ -546,12 +537,11 @@
    /**
     * The protected constructor.
     *
     * @param reader  the BufferedReader of the stop process.
     * @param isError a boolean indicating whether the BufferedReader
     * @param stream  the stream of the stop process to read.
     * @param isError a boolean indicating whether the stream
     *        corresponds to the standard error or to the standard output.
     */
    public StopReader(final BufferedReader reader,
                                      final boolean isError) {
    public StopReader(final InputStream stream, final boolean isError) {
      final LocalizableMessage errorTag =
              isError ?
                      INFO_ERROR_READING_ERROROUTPUT.get() :
@@ -561,8 +551,9 @@
      Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
          try {
            String line = reader.readLine();
          // The reader is owned by this thread, which closes it once the stream is drained.
          try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) {
            String line = in.readLine();
            while (line != null) {
              if (application != null) {
                LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
@@ -581,7 +572,7 @@
                isFirstLine = false;
              }
              logger.info(LocalizableMessage.raw("server: " + line));
              line = reader.readLine();
              line = in.readLine();
            }
          } catch (Throwable t) {
            if (application != null) {
@@ -625,13 +616,13 @@
    /**
     * The protected constructor.
     * @param reader the BufferedReader of the start process.
     * @param stream the stream of the start process to read.
     * @param startedId the message ID that this class can use to know whether
     * the start is over or not.
     * @param isError a boolean indicating whether the BufferedReader
     * @param isError a boolean indicating whether the stream
     * corresponds to the standard error or to the standard output.
     */
    public StartReader(final BufferedReader reader, final String startedId,
    public StartReader(final InputStream stream, final String startedId,
        final boolean isError)
    {
      final LocalizableMessage errorTag =
@@ -646,9 +637,10 @@
        @Override
        public void run()
        {
          try
          // The reader is owned by this thread, which closes it once the stream is drained.
          try (BufferedReader in = new BufferedReader(new InputStreamReader(stream)))
          {
            String line = reader.readLine();
            String line = in.readLine();
            while (line != null)
            {
              if (application != null) {
@@ -676,7 +668,7 @@
                isFinished = true;
                startedIdFound = true;
              }
              line = reader.readLine();
              line = in.readLine();
            }
          } catch (Throwable t)
          {
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/Utils.java
@@ -166,40 +166,42 @@
      final Process process = pb.start();
      logger.info(LocalizableMessage.raw("launching " + args + " with env: " + env));
      InputStream is = process.getInputStream();
      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
      String line;
      boolean errorDetected = false;
      while (null != (line = reader.readLine()))
      try (BufferedReader reader = new BufferedReader(new InputStreamReader(is)))
      {
        logger.info(LocalizableMessage.raw("The output: " + line));
        if (line.contains("ERROR:  The detected Java version"))
        String line;
        while (null != (line = reader.readLine()))
        {
          if (isWindows())
          logger.info(LocalizableMessage.raw("The output: " + line));
          if (line.contains("ERROR:  The detected Java version"))
          {
            // If we are running windows, the process get blocked waiting for
            // user input.  Just wait for a certain time to print the output
            // in the logger and then kill the process.
            Thread t = new Thread(new Runnable()
            if (isWindows())
            {
              @Override
              public void run()
              // If we are running windows, the process get blocked waiting for
              // user input.  Just wait for a certain time to print the output
              // in the logger and then kill the process.
              Thread t = new Thread(new Runnable()
              {
                try
                @Override
                public void run()
                {
                  Thread.sleep(3000);
                  // To see if the process is over, call the exitValue method.
                  // If it is not over, a IllegalThreadStateException.
                  process.exitValue();
                  try
                  {
                    Thread.sleep(3000);
                    // To see if the process is over, call the exitValue method.
                    // If it is not over, a IllegalThreadStateException.
                    process.exitValue();
                  }
                  catch (Throwable t)
                  {
                    process.destroy();
                  }
                }
                catch (Throwable t)
                {
                  process.destroy();
                }
              }
            });
            t.start();
              });
              t.start();
            }
            errorDetected = true;
          }
          errorDetected = true;
        }
      }
      process.waitFor();
opendj-server-legacy/src/main/java/org/opends/quicksetup/util/ZipExtractor.java
@@ -100,15 +100,22 @@
                                      Application app)
    throws FileNotFoundException, IllegalArgumentException
  {
    this(new FileInputStream(zipFile),
    // The name is validated before the stream is opened, otherwise the stream would leak when the
    // validation fails.
    this(openZipFile(zipFile),
      minRatio,
      maxRatio,
      numberZipEntries,
      zipFile.getName(),
      app);
  }
  private static FileInputStream openZipFile(File zipFile) throws FileNotFoundException
  {
    if (!zipFile.getName().endsWith(".zip")) {
      throw new IllegalArgumentException("File must have extension .zip");
    }
    return new FileInputStream(zipFile);
  }
  /**
opendj-server-legacy/src/main/java/org/opends/server/api/ConnectionHandler.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.api;
@@ -23,6 +24,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg;
@@ -53,6 +55,20 @@
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /**
   * Maximum time a {@code start()} implementation waits for its handler thread
   * to attempt to open the listen socket. The wait is bounded so that a handler
   * thread dying before it reaches the listen code cannot hang the whole server
   * startup.
   * <p>
   * {@code DirectoryServer.startConnectionHandlers()} starts the handlers one
   * after another, so the worst case for a start is this value multiplied by
   * the number of configured handlers. It is therefore kept far below the
   * {@code start-ds} timeout ({@code DirectoryServer.DEFAULT_TIMEOUT}, 200
   * seconds), which is generous for a {@code bind()}.
   */
  protected static final long LISTEN_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(15);
  /** The monitor associated with this connection handler. */
  private ConnectionHandlerMonitor monitor;
opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/AciHandler.java
@@ -15,6 +15,7 @@
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2013 Manuel Gaupp
 * Portions Copyright 2024 3A Systems, LLC.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.authorization.dseecompat;
@@ -729,6 +730,9 @@
              }
            }
            break;
          default:
            // No action needed for the remaining values.
            break;
          }
          /*
           * Check if the modification type has an "aci" attribute type.
opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/BindRule.java
@@ -415,6 +415,10 @@
                throw new AciException(
                    WARN_ACI_SYNTAX_INVALID_BIND_RULE_KEYWORD_OPERATOR_COMBO.get(keyword, op));
            }
            break;
        default:
          // No action needed for the remaining values.
          break;
        }
    }
@@ -525,11 +529,9 @@
     *          should be appended.
     */
    public final void toString(StringBuilder buffer) {
        if (this.keywordRuleMap != null) {
            for (KeywordBindRule rule : this.keywordRuleMap.values()) {
                rule.toString(buffer);
                buffer.append(";");
            }
        for (KeywordBindRule rule : this.keywordRuleMap.values()) {
            rule.toString(buffer);
            buffer.append(";");
        }
    }
}
opendj-server-legacy/src/main/java/org/opends/server/authorization/dseecompat/UserAttr.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.authorization.dseecompat;
@@ -148,6 +149,9 @@
                  //a message if it is seen in the expression.
                  throw new AciException(WARN_ACI_SYNTAX_ROLEDN_NOT_SUPPORTED.get(expression));
                }
                default:
                  // No action needed for the remaining values.
                  break;
         }
         return new UserAttr(vals[0], vals[1], userAttrType, type);
    }
opendj-server-legacy/src/main/java/org/opends/server/backends/LDIFBackend.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2007-2008 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.backends;
@@ -366,12 +367,7 @@
    try
    {
      if (entryMap != null)
      {
        return entryMap.size();
      }
      return -1;
      return entryMap.size();
    }
    finally
    {
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/DN2URI.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.backends.pluggable;
@@ -332,6 +333,9 @@
              update(txn, entryDN, toStrings(a));
            }
            break;
          default:
            // No action needed for the remaining values.
            break;
        }
      }
    }
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/ID2Entry.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.backends.pluggable;
@@ -259,30 +260,24 @@
        {
          return Entry.decode(reader, compressedSchema);
        }
        InputStream is = reader.asInputStream();
        if ((format & ENCRYPT_ENTRY) == ENCRYPT_ENTRY)
        try (InputStream entryStream = newEntryInputStream(reader, format))
        {
          is = getCryptoManager().getCipherInputStream(is);
        }
        if ((format & COMPRESS_ENTRY) == COMPRESS_ENTRY)
        {
          is = new InflaterInputStream(is);
        }
        byte[] data = new byte[encodedEntryLen];
        int readBytes;
        int position = 0;
        int leftToRead = encodedEntryLen;
        // CipherInputStream does not read more than block size...
        do
        {
          if ((readBytes = is.read(data, position, leftToRead)) == -1 )
          byte[] data = new byte[encodedEntryLen];
          int readBytes;
          int position = 0;
          int leftToRead = encodedEntryLen;
          // CipherInputStream does not read more than block size...
          do
          {
            throw DecodeException.error(ERR_CANNOT_DECODE_ENTRY.get());
          }
          position += readBytes;
          leftToRead -= readBytes;
        } while (leftToRead > 0 && readBytes > 0);
        return Entry.decode(ByteString.wrap(data).asReader(), compressedSchema);
            if ((readBytes = entryStream.read(data, position, leftToRead)) == -1 )
            {
              throw DecodeException.error(ERR_CANNOT_DECODE_ENTRY.get());
            }
            position += readBytes;
            leftToRead -= readBytes;
          } while (leftToRead > 0 && readBytes > 0);
          return Entry.decode(ByteString.wrap(data).asReader(), compressedSchema);
        }
      }
      catch (CryptoManagerException cme)
      {
@@ -291,6 +286,31 @@
      }
    }
    /**
     * Returns the stream to read the encoded entry from, decorated as mandated by the format.
     * <p>
     * The returned stream owns the streams it wraps, so closing it closes the whole chain.
     *
     * @param reader the reader positioned at the start of the encoded entry.
     * @param format the format byte of the encoded entry.
     * @return the stream to read the encoded entry from.
     * @throws CryptoManagerException If the cipher input stream cannot be created.
     */
    private static InputStream newEntryInputStream(ByteSequenceReader reader, int format)
        throws CryptoManagerException
    {
      InputStream is = reader.asInputStream();
      if ((format & ENCRYPT_ENTRY) == ENCRYPT_ENTRY)
      {
        is = getCryptoManager().getCipherInputStream(is);
      }
      if ((format & COMPRESS_ENTRY) == COMPRESS_ENTRY)
      {
        is = new InflaterInputStream(is);
      }
      return is;
    }
    private ByteString encode(Entry entry, DataConfig dataConfig) throws DirectoryException
    {
      encodeVolatile(entry, dataConfig);
opendj-server-legacy/src/main/java/org/opends/server/backends/pluggable/RootContainer.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.backends.pluggable;
@@ -229,7 +230,8 @@
      }
    }
    nextEntryID = new AtomicLong(highestID.longValue() + 1);
    // highestID is null when there was no base DN to open, in which case numbering starts at 1.
    nextEntryID = new AtomicLong(highestID != null ? highestID.longValue() + 1 : 1);
  }
  /**
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskBackend.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.backends.task;
@@ -761,6 +762,9 @@
          searchScheduledTasks  = true;
          searchRecurringTasks  = true;
          break;
        default:
          // No action needed for the remaining values.
          break;
      }
    }
    else if (baseDN.equals(scheduledTaskParentDN))
@@ -780,6 +784,9 @@
        case SUBORDINATES:
          searchScheduledTasks  = true;
          break;
        default:
          // No action needed for the remaining values.
          break;
      }
    }
    else if (baseDN.equals(recurringTaskParentDN))
@@ -799,6 +806,9 @@
        case SUBORDINATES:
          searchRecurringTasks  = true;
          break;
        default:
          // No action needed for the remaining values.
          break;
      }
    }
    else
opendj-server-legacy/src/main/java/org/opends/server/backends/task/TaskThread.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2013-2014 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.backends.task;
@@ -102,7 +103,7 @@
    synchronized (notifyLock)
    {
      notifyLock.notify();
      notifyLock.notifyAll();
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/config/ConfigurationHandler.java
@@ -12,7 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2025 3A Systems,LLC
 * Portions Copyright 2025-2026 3A Systems, LLC.
 */
package org.opends.server.config;
@@ -1405,7 +1405,8 @@
  {
    byte[] buffer = new byte[8192];
    try(FileInputStream inputStream = new FileInputStream(configFile);
        GZIPOutputStream outputStream = new GZIPOutputStream(new FileOutputStream(archiveFile)))
        FileOutputStream archiveStream = new FileOutputStream(archiveFile);
        GZIPOutputStream outputStream = new GZIPOutputStream(archiveStream))
    {
      int bytesRead = inputStream.read(buffer);
      while (bytesRead > 0)
opendj-server-legacy/src/main/java/org/opends/server/core/BindOperationBasis.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2007-2010 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.core;
@@ -516,6 +517,10 @@
          {
            bindDN = actualRootDN;
          }
          break;
        default:
          // No action needed for the remaining values.
          break;
      }
      workflowExecuted = execute(this, bindDN);
opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
@@ -3590,6 +3590,9 @@
         }
         break;
        default:
          // No action needed for the remaining values.
          break;
      }
    }
@@ -3655,6 +3658,9 @@
          // Modify may or may not be allowed, but we'll leave that
          // determination up to the modify operation itself.
        default:
          // No action needed for the remaining values.
          break;
      }
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/core/MemoryQuota.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2015 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.core;
@@ -132,7 +133,7 @@
   */
  public long memPercentToBytes(int percent)
  {
    return (reservableMemory * percent / 100) * ONE_MEGABYTE;
    return ((long) reservableMemory * percent / 100) * ONE_MEGABYTE;
  }
  /**
opendj-server-legacy/src/main/java/org/opends/server/core/ModifyDNOperationBasis.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.core;
@@ -92,6 +93,12 @@
  private DN newDN;
  /**
   * Indicates whether the new entry DN has already been computed. The computation does not always
   * yield a DN, and its failure is recorded on the operation, so it must not be repeated.
   */
  private boolean newDNComputed;
  /**
   * Creates a new modify DN operation with the provided information.
   *
   * @param  clientConnection  The client connection with which this operation
@@ -188,6 +195,9 @@
    this.rawEntryDN = rawEntryDN;
    entryDN = null;
    // The new DN is derived from the entry DN, so a failed computation must be allowed to run
    // again. Any DN already computed is left in place, as it was before this flag existed.
    newDNComputed = false;
  }
  @Override
@@ -228,6 +238,7 @@
    newRDN = null;
    newDN = null;
    newDNComputed = false;
  }
  @Override
@@ -275,6 +286,7 @@
    newSuperior = null;
    newDN = null;
    newDNComputed = false;
  }
  @Override
@@ -510,17 +522,22 @@
  @Override
  public DN getNewDN()
  {
    if (newDN == null)
    if (newDN == null && !newDNComputed)
    {
      newDNComputed = true;
      // Construct the new DN to use for the entry.
      DN parentDN = null;
      DN parentDN;
      if (getNewSuperior() == null)
      {
        if (getEntryDN() != null)
        if (getEntryDN() == null)
        {
          parentDN = DirectoryServer.getInstance().getServerContext().getBackendConfigManager()
              .getParentDNInSuffix(entryDN);
          // The raw DN could not be parsed and INVALID_DN_SYNTAX has already been recorded for it,
          // so leave that diagnosis in place rather than replacing it with a less precise one.
          return null;
        }
        parentDN = DirectoryServer.getInstance().getServerContext().getBackendConfigManager()
            .getParentDNInSuffix(entryDN);
      }
      else
      {
@@ -531,6 +548,11 @@
      {
        setResultCode(ResultCode.UNWILLING_TO_PERFORM);
        appendErrorMessage(ERR_MODDN_NO_PARENT.get(entryDN));
        if (parentDN == null)
        {
          // Without a parent there is no new DN to build; the error set above is the outcome.
          return null;
        }
      }
      newDN = parentDN.child(getNewRDN());
    }
opendj-server-legacy/src/main/java/org/opends/server/core/SearchOperationBasis.java
@@ -911,7 +911,7 @@
  }
  @Override
  public void abort(CancelRequest cancelRequest)
  public synchronized void abort(CancelRequest cancelRequest)
  {
    if(cancelResult == null && this.cancelRequest == null)
    {
@@ -1216,6 +1216,9 @@
        }
      }
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
    return false;
opendj-server-legacy/src/main/java/org/opends/server/crypto/CryptoManagerImpl.java
@@ -14,6 +14,7 @@
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2009 Parametric Technology Corporation (PTC)
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.crypto;
@@ -312,7 +313,7 @@
    // Requested encryption cipher validation.
    String requestedCipherTransformation = cfg.getCipherTransformation();
    Integer requestedCipherTransformationKeyLengthBits = cfg.getCipherKeyLength();
    int requestedCipherTransformationKeyLengthBits = cfg.getCipherKeyLength();
    if (! requestedCipherTransformation.equals(
            this.preferredCipherTransformation) ||
        requestedCipherTransformationKeyLengthBits !=
@@ -341,7 +342,7 @@
    // Requested MAC algorithm validation.
    String requestedMACAlgorithm = cfg.getMacAlgorithm();
    Integer requestedMACAlgorithmKeyLengthBits = cfg.getMacKeyLength();
    int requestedMACAlgorithmKeyLengthBits = cfg.getMacKeyLength();
    if (!requestedMACAlgorithm.equals(this.preferredMACAlgorithm) ||
         requestedMACAlgorithmKeyLengthBits !=
              this.preferredMACAlgorithmKeyLengthBits)
opendj-server-legacy/src/main/java/org/opends/server/extensions/DynamicGroup.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -257,7 +258,7 @@
  public MemberList getMembers()
         throws DirectoryException
  {
    return new DynamicGroupMemberList(groupEntryDN, memberURLs);
    return new DynamicGroupMemberList(groupEntryDN, memberURLs).start();
  }
  @Override
@@ -267,12 +268,12 @@
  {
    if (baseDN == null && filter == null)
    {
      return new DynamicGroupMemberList(groupEntryDN, memberURLs);
      return new DynamicGroupMemberList(groupEntryDN, memberURLs).start();
    }
    else
    {
      return new DynamicGroupMemberList(groupEntryDN, memberURLs, baseDN, scope,
                                        filter);
                                        filter).start();
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/extensions/DynamicGroupMemberList.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -60,6 +61,9 @@
   */
  private final LinkedBlockingQueue<Object> resultQueue;
  /** The background thread which performs the internal searches. */
  private final DynamicGroupSearchThread searchThread;
  /** The search filter to use when filtering the set of group members. */
  private final SearchFilter filter;
@@ -292,9 +296,22 @@
      }
    }
    DynamicGroupSearchThread searchThread =
    searchThread =
         new DynamicGroupSearchThread(this, baseDNArray, filterArray, urlArray);
  }
  /**
   * Starts the background searches which populate this member list.
   * <p>
   * The searches are not started by the constructor so that {@code this} is not published to the
   * search thread before construction has completed.
   *
   * @return this member list
   */
  DynamicGroupMemberList start()
  {
    searchThread.start();
    return this;
  }
  /**
opendj-server-legacy/src/main/java/org/opends/server/extensions/EntryCacheCommon.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -362,11 +363,11 @@
      // Cache misses is required to get cache tries and hit ratio.
      if (cacheMisses != null)
      {
        Long cacheTries = cacheHits + cacheMisses;
        long cacheTries = cacheHits + cacheMisses;
        attrs.add("entryCacheTries", cacheTries);
        Double hitRatioRaw = cacheTries > 0 ? cacheHits.doubleValue()
            / cacheTries.doubleValue() : cacheHits.doubleValue() / 1;
        double hitRatioRaw = cacheTries > 0 ? cacheHits.doubleValue()
            / cacheTries : cacheHits.doubleValue();
        Double hitRatio = hitRatioRaw * 100D;
        attrs.add("entryCacheHitRatio", hitRatio);
      }
opendj-server-legacy/src/main/java/org/opends/server/extensions/EntryDNVirtualAttributeProvider.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2009 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -283,6 +284,9 @@
          }
        }
        break;
      default:
        // No action needed for the remaining values.
        break;
    }
  }
}
opendj-server-legacy/src/main/java/org/opends/server/extensions/ExternalSASLMechanismHandler.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -278,6 +279,9 @@
            return;
          }
        }
      default:
        // No action needed for the remaining values.
        break;
    }
opendj-server-legacy/src/main/java/org/opends/server/extensions/IsMemberOfVirtualAttributeProvider.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2009 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -343,6 +344,9 @@
          }
        }
        break;
      default:
        // No action needed for the remaining values.
        break;
    }
    return null;
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA1PasswordStorageScheme.java
@@ -60,6 +60,13 @@
  /** The number of bytes of random data to use as the salt when generating the hashes. */
  private static final int NUM_SALT_BYTES = 8;
  /**
   * Source of randomness for the offline encoding, which has no access to the server's
   * crypto manager. A single instance is shared because {@code SecureRandom} is thread-safe
   * and reseeding it for every password would weaken it.
   */
  private static final SecureRandom OFFLINE_RANDOM = new SecureRandom();
  /** The number of bytes SHA algorithm produces. */
  private static final int SHA1_LENGTH = 20;
@@ -423,7 +430,7 @@
         throws DirectoryException
  {
    byte[] saltBytes = new byte[NUM_SALT_BYTES];
    new SecureRandom().nextBytes(saltBytes);
    OFFLINE_RANDOM.nextBytes(saltBytes);
    byte[] passwordPlusSalt = new byte[passwordBytes.length + NUM_SALT_BYTES];
    System.arraycopy(passwordBytes, 0, passwordPlusSalt, 0,
opendj-server-legacy/src/main/java/org/opends/server/extensions/SaltedSHA512PasswordStorageScheme.java
@@ -60,6 +60,13 @@
  /** The number of bytes of random data to use as the salt when generating the hashes. */
  private static final int NUM_SALT_BYTES = 8;
  /**
   * Source of randomness for the offline encoding, which has no access to the server's
   * crypto manager. A single instance is shared because {@code SecureRandom} is thread-safe
   * and reseeding it for every password would weaken it.
   */
  private static final SecureRandom OFFLINE_RANDOM = new SecureRandom();
  /** The size of the digest in bytes. */
  private static final int SHA512_LENGTH = 512 / 8;
@@ -427,7 +434,7 @@
         throws DirectoryException
  {
    byte[] saltBytes = new byte[NUM_SALT_BYTES];
    new SecureRandom().nextBytes(saltBytes);
    OFFLINE_RANDOM.nextBytes(saltBytes);
    byte[] passwordPlusSalt = new byte[passwordBytes.length + NUM_SALT_BYTES];
    System.arraycopy(passwordBytes, 0, passwordPlusSalt, 0,
opendj-server-legacy/src/main/java/org/opends/server/extensions/StaticGroup.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.extensions;
@@ -599,6 +600,9 @@
                }
              }
              break;
            default:
              // No action needed for the remaining values.
              break;
          }
        }
      }
opendj-server-legacy/src/main/java/org/opends/server/loggers/AsynchronousTextWriter.java
@@ -43,7 +43,7 @@
  private String name;
  private AtomicBoolean stopRequested;
  private WriterThread writerThread;
  private final WriterThread writerThread;
  private boolean autoFlush;
@@ -65,16 +65,28 @@
    this.queue = new LinkedBlockingQueue<>(capacity);
    this.capacity = capacity;
    this.writerThread = null;
    this.stopRequested = new AtomicBoolean(false);
    writerThread = new WriterThread();
    writerThread.start();
    DirectoryServer.registerShutdownListener(this);
  }
  /**
   * Starts the background thread which publishes the queued log records.
   * <p>
   * The thread is not started by the constructor so that {@code this} is not published to it before
   * construction has completed.
   *
   * @return this writer
   */
  AsynchronousTextWriter start()
  {
    writerThread.start();
    return this;
  }
  /**
   * The publisher thread is responsible for emptying the queue of log records
   * waiting to published.
   */
@@ -216,7 +228,7 @@
    stopRequested.set(true);
    // Wait for publisher thread to terminate
    while (writerThread != null && writerThread.isAlive()) {
    while (writerThread.isAlive()) {
      try {
        // Interrupt the thread if its blocking
        writerThread.interrupt();
opendj-server-legacy/src/main/java/org/opends/server/loggers/CommonAudit.java
@@ -358,7 +358,7 @@
      throws IOException, AuditException, ConfigException
  {
    final JsonValue jsonConfig;
    try (InputStream input = getClass().getResourceAsStream(AUDIT_SERVICE_JSON_CONFIGURATION_FILE))
    try (InputStream input = CommonAudit.class.getResourceAsStream(AUDIT_SERVICE_JSON_CONFIGURATION_FILE))
    {
      jsonConfig = AuditJsonConfig.getJson(input);
    }
opendj-server-legacy/src/main/java/org/opends/server/loggers/CommonAuditAccessLogPublisher.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -501,7 +502,7 @@
  private Pair<Long,TimeUnit> getExecutionTime(final Operation operation)
  {
    Long etime = operation.getProcessingNanoTime();
    long etime = operation.getProcessingNanoTime();
    // if not configured for nanos, use millis
    return etime <= -1 ?
        Pair.of(operation.getProcessingTime(), TimeUnit.MILLISECONDS) :
opendj-server-legacy/src/main/java/org/opends/server/loggers/JDKLogging.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -112,14 +113,16 @@
    @Override
    public void publish(LogRecord record)
    {
      if (getFormatter() == null)
      // These calls resolve to Handler's own formatter, not to the enclosing
      // JDKLogging.getFormatter(); qualify them so that is unambiguous.
      if (super.getFormatter() == null)
      {
        setFormatter(new SimpleFormatter());
        super.setFormatter(new SimpleFormatter());
      }
      try
      {
        String message = getFormatter().format(record);
        String message = super.getFormatter().format(record);
        if (record.getLevel().intValue() >= Level.WARNING.intValue())
        {
          err.write(message.getBytes());
opendj-server-legacy/src/main/java/org/opends/server/loggers/MultifileTextWriter.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -134,12 +135,25 @@
    this.stopRequested = false;
    rotaterThread = new RotaterThread(this);
    rotaterThread.start();
    DirectoryServer.registerShutdownListener(this);
  }
  /**
   * Starts the background thread which rotates and retains the log files.
   * <p>
   * The thread is not started by the constructor so that {@code this} is not published to it before
   * construction has completed.
   *
   * @return this writer
   */
  MultifileTextWriter start()
  {
    rotaterThread.start();
    return this;
  }
  /**
   * Construct a PrintWriter for a file.
   * @param file - the file to open for writing
   * @param filePermissions - the file permissions to set on the file.
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextAccessLogPublisher.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -242,6 +243,9 @@
        theWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
      // Rotation only starts once the policies above are registered.
      theWriter.start();
      if (cfg.isAsynchronous())
      {
        this.writer = newAsyncWriter(theWriter, cfg);
@@ -285,7 +289,7 @@
  private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter mfWriter, FileBasedAccessLogPublisherCfg config)
  {
    String name = "Asynchronous Text Writer for " + config.dn();
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter);
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter).start();
  }
  @Override
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextAuditLogPublisher.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -202,6 +203,9 @@
        writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
      // Rotation only starts once the policies above are registered.
      writer.start();
      if (cfg.isAsynchronous())
      {
        this.writer = newAsyncWriter(writer, cfg);
@@ -230,7 +234,7 @@
  private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter writer, FileBasedAuditLogPublisherCfg cfg)
  {
    String name = "Asynchronous Text Writer for " + cfg.dn();
    return new AsynchronousTextWriter(name, cfg.getQueueSize(), cfg.isAutoFlush(), writer);
    return new AsynchronousTextWriter(name, cfg.getQueueSize(), cfg.isAutoFlush(), writer).start();
  }
  @Override
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextDebugLogPublisher.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -133,6 +134,9 @@
        writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
      // Rotation only starts once the policies above are registered.
      writer.start();
      if(config.isAsynchronous())
      {
        this.writer = newAsyncWriter(writer, config);
@@ -271,7 +275,7 @@
  private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter writer, FileBasedDebugLogPublisherCfg config)
  {
    String name = "Asynchronous Text Writer for " + config.dn();
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), writer);
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), writer).start();
  }
  private void configure(MultifileTextWriter mfWriter, FileBasedDebugLogPublisherCfg config) throws DirectoryException
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextErrorLogPublisher.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -126,6 +127,9 @@
        writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
      // Rotation only starts once the policies above are registered.
      writer.start();
      if(config.isAsynchronous())
      {
        this.writer = newAsyncWriter(writer, config);
@@ -396,7 +400,7 @@
  private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter mfWriter, FileBasedErrorLogPublisherCfg config)
  {
    String name = "Asynchronous Text Writer for " + config.dn();
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter);
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter).start();
  }
  private void setDefaultSeverities(Set<ErrorLogPublisherCfgDefn.DefaultSeverity> defSevs)
opendj-server-legacy/src/main/java/org/opends/server/loggers/TextHTTPAccessLogPublisher.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.loggers;
@@ -275,7 +276,7 @@
  private AsynchronousTextWriter newAsyncWriter(MultifileTextWriter mfWriter, FileBasedHTTPAccessLogPublisherCfg config)
  {
    String name = "Asynchronous Text Writer for " + config.dn();
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter);
    return new AsynchronousTextWriter(name, config.getQueueSize(), config.isAutoFlush(), mfWriter).start();
  }
  private LocalizableMessage setLogFormatFields(String logFormat)
@@ -336,6 +337,9 @@
        theWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
      }
      // Rotation only starts once the policies above are registered.
      theWriter.start();
      if (cfg.isAsynchronous())
      {
        this.writer = newAsyncWriter(theWriter, cfg);
opendj-server-legacy/src/main/java/org/opends/server/plugins/PasswordPolicyImportPlugin.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.plugins;
@@ -240,6 +241,9 @@
      case USER_PASSWORD:
        userPWTypes.add(t);
        break;
      default:
        // No action needed for the remaining values.
        break;
      }
    }
opendj-server-legacy/src/main/java/org/opends/server/plugins/profiler/ProfileViewer.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.plugins.profiler;
@@ -214,11 +215,10 @@
   */
  public void processDataFile(String filename) throws IOException
  {
    // Try to open the file for reading.
    ASN1Reader reader = ASN1.getReader(new FileInputStream(filename));
    try
    // Both resources are declared here so that the file stream is closed as well if the reader
    // cannot be created around it.
    try (FileInputStream fileStream = new FileInputStream(filename);
         ASN1Reader reader = ASN1.getReader(fileStream))
    {
      // The first element in the file must be a sequence with the header
      // information.
@@ -273,10 +273,6 @@
        existingFrame.recurseSubFrames(stack, pos-1, count, stacksByMethod);
      }
    }
    finally
    {
      close(reader);
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/plugins/profiler/ProfilerPlugin.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.plugins.profiler;
import static org.opends.messages.PluginMessages.*;
@@ -352,6 +353,9 @@
          }
        }
        break;
      default:
        // No action needed for the remaining values.
        break;
    }
    return ccr;
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.protocols.http;
@@ -165,6 +166,14 @@
   */
  private final Object waitListen = new Object();
  /**
   * Condition predicate for {@link #waitListen}: set once the handler thread
   * has attempted to start the embedded HTTP server (successfully or not).
   * Guarded by the {@link #waitListen} monitor; protects the start method
   * against spurious wakeups.
   */
  private boolean listenAttempted;
  /** The friendly name of this connection handler. */
  private String friendlyName;
@@ -567,6 +576,14 @@
    return httpServer != null;
  }
  /**
   * {@inheritDoc}
   * <p>
   * Deliberately left unsynchronized, unlike the overridden {@link Thread#start()}:
   * the state this method touches is guarded by the {@link #waitListen} monitor, and
   * holding the thread's own monitor across {@code waitListen.wait()} would block
   * {@link Thread#join()}.
   */
  @Override
  public void start()
  {
@@ -577,13 +594,25 @@
    {
      super.start();
      final long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(LISTEN_TIMEOUT_MS);
      try
      {
        waitListen.wait();
        while (!listenAttempted)
        {
          final long remainingMs = TimeUnit.NANOSECONDS.toMillis(deadlineNanos - System.nanoTime());
          if (remainingMs <= 0)
          {
            logger.error(ERR_CONNHANDLER_TIMEOUT_WAITING_FOR_LISTENER, handlerName,
                TimeUnit.MILLISECONDS.toSeconds(LISTEN_TIMEOUT_MS));
            break;
          }
          waitListen.wait(remainingMs);
        }
      }
      catch (InterruptedException e)
      {
        // If something interrupted the start its probably better to return ASAP
        Thread.currentThread().interrupt();
      }
    }
  }
@@ -625,7 +654,8 @@
          synchronized (waitListen)
          {
            starting = false;
            waitListen.notify();
            listenAttempted = true;
            waitListen.notifyAll();
          }
        }
@@ -642,13 +672,6 @@
      try
      {
        // At this point, the connection Handler either started correctly or failed
        // to start but the start process should be notified and resume its work in any cases.
        synchronized (waitListen)
        {
          waitListen.notify();
        }
        // If we have gotten here, then we are about to start listening
        // for the first time since startup or since we were previously disabled.
        // Start the embedded HTTP server
@@ -682,6 +705,16 @@
          lastIterationFailed = true;
        }
      }
      finally
      {
        // At this point, the connection handler either started correctly or failed to start
        // but the start process should be notified and resume its work in any cases.
        synchronized (waitListen)
        {
          listenAttempted = true;
          waitListen.notifyAll();
        }
      }
    }
    // Initiate shutdown
opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPConnectionHandler.java
@@ -873,7 +873,7 @@
          {
            starting = false;
            listenAttempted = true;
            waitListen.notify();
            waitListen.notifyAll();
          }
        }
@@ -897,7 +897,7 @@
        synchronized (waitListen)
        {
          listenAttempted = true;
          waitListen.notify();
          waitListen.notifyAll();
        }
        // If none of the listeners were created successfully, then
opendj-server-legacy/src/main/java/org/opends/server/replication/common/StatusMachine.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.common;
@@ -39,6 +40,9 @@
      case DEGRADED_STATUS:
      case BAD_GEN_ID_STATUS:
        return true;
      default:
        // No action needed for the remaining values.
        break;
    }
    return false;
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalMultiple.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;
@@ -330,6 +331,9 @@
    case INCREMENT:
      /* FIXME : we should update CSN */
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/AttrHistoricalSingle.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.plugin;
@@ -102,6 +103,9 @@
    case INCREMENT:
      /* FIXME : we should update CSN */
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
  }
@@ -261,6 +265,9 @@
    case INCREMENT:
      /* FIXME : we should update CSN */
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
    return conflict;
  }
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/LDAPReplicationDomain.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;
@@ -2239,7 +2240,7 @@
        flushThread.initiateShutdown();
        synchronized (flushThread)
        {
          flushThread.notify();
          flushThread.notifyAll();
        }
      }
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/ReplicationServerListener.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.plugin;
@@ -56,15 +57,28 @@
      ReplicationSynchronizationProviderCfg configuration,
      DSRSShutdownSync dsrsShutdownSync) throws ConfigException
  {
    configuration.addReplicationServerAddListener(this);
    configuration.addReplicationServerDeleteListener(this);
    this.dsrsShutdownSync = dsrsShutdownSync;
    if (configuration.hasReplicationServer())
    {
      final ReplicationServerCfg cfg = configuration.getReplicationServer();
      // Registered only once the replication server exists: the listeners are held by the
      // server wide configuration repository and keyed by DN, so a listener registered by
      // a listener which is then discarded would be leaked until the server is restarted.
      replicationServer = new ReplicationServer(cfg, dsrsShutdownSync);
    }
    try
    {
      configuration.addReplicationServerAddListener(this);
      configuration.addReplicationServerDeleteListener(this);
    }
    catch (ConfigException e)
    {
      // This listener is not returned to MultimasterReplication, which is what stops the
      // replication server: nothing would ever shut down the one just created.
      shutdown();
      throw e;
    }
  }
  /** {@inheritDoc} */
@@ -78,9 +92,11 @@
    }
    catch (ConfigException e)
    {
      // we should never get to this point because the configEntry has
      // already been validated in configAddisAcceptable
      // The configEntry has already been validated in configAddisAcceptable, but the
      // listen port may have been taken since then: report why the creation failed. The
      // message is logged by the configuration handler, which reports the result.
      ccr.setResultCode(ResultCode.CONSTRAINT_VIOLATION);
      ccr.addMessage(e.getMessageObject());
    }
    return ccr;
  }
opendj-server-legacy/src/main/java/org/opends/server/replication/server/MessageHandler.java
@@ -130,7 +130,7 @@
       */
      if (msgQueue.isEmpty())
      {
        msgQueue.notify();
        msgQueue.notifyAll();
      }
      msgQueue.add(update);
@@ -644,7 +644,6 @@
    synchronized (msgQueue)
    {
      msgQueue.clear();
      msgQueue.notify();
      msgQueue.notifyAll();
    }
opendj-server-legacy/src/main/java/org/opends/server/replication/server/ReplicationServer.java
@@ -39,8 +39,10 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
@@ -92,7 +94,18 @@
{
  private String serverURL;
  private ServerSocket listenSocket;
  /**
   * Number of attempts to bind the listen port before giving up. The port may be held for a
   * short while by a socket which is being closed, so a few retries make the start-up
   * resilient to such transient conditions.
   */
  private static final int LISTEN_BIND_ATTEMPTS = 5;
  /** Delay between two attempts to bind the listen port. */
  private static final long LISTEN_BIND_RETRY_DELAY_MS = 200;
  /** Timeout of the diagnostic probe performed when the listen port cannot be bound. */
  private static final int LISTEN_PORT_PROBE_TIMEOUT_MS = 200;
  private volatile ServerSocket listenSocket;
  private Thread listenThread;
  private Thread connectThread;
@@ -109,8 +122,16 @@
  /** The backend that allow to search the changes (external changelog). */
  private ChangelogBackend changelogBackend;
  /**
   * Whether this instance registered the virtual attribute rules of the external changelog.
   * They are registered globally, by attribute name, so an instance which did not register
   * them must not deregister them: it would strip them from the instance which did.
   */
  private boolean externalChangelogRegistered;
  private final AtomicBoolean shutdown = new AtomicBoolean();
  private boolean stopListen;
  /** Written by the thread applying a configuration change, read by the listen thread. */
  private volatile boolean stopListen;
  private final ReplSessionSecurity replSessionSecurity;
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -124,6 +145,16 @@
   */
  private static final Set<Integer> localPorts = new CopyOnWriteArraySet<>();
  /**
   * Number of attempts to bind a listen port which failed in this VM.
   * <p>
   * This is required for unit testing: it lets a test which holds a listen port release it
   * as soon as a replication server has actually failed to bind it, instead of after a
   * delay which would make the test vacuous when it is too long, and flaky when it is too
   * short.
   */
  static final AtomicInteger listenPortBindFailures = new AtomicInteger();
  /** Monitors for synchronizing domain creation with the connect thread. */
  private final Object domainTicketLock = new Object();
  private final Object connectThreadLock = new Object();
@@ -177,15 +208,25 @@
    this.dsrsShutdownSync = dsrsShutdownSync;
    this.domainPredicate = predicate;
    enableExternalChangeLog();
    ServerContext serverContext = DirectoryServer.getInstance().getServerContext();
    cryptoSuite = serverContext.getCryptoManager().
        newCryptoSuite(cfg.getCipherTransformation(), cfg.getCipherKeyLength(), cfg.isConfidentialityEnabled());
    try
    {
      enableExternalChangeLog();
      ServerContext serverContext = DirectoryServer.getInstance().getServerContext();
      cryptoSuite = serverContext.getCryptoManager().
          newCryptoSuite(cfg.getCipherTransformation(), cfg.getCipherKeyLength(), cfg.isConfidentialityEnabled());
    this.changelogDB = new FileChangelogDB(this, config.getReplicationDBDirectory(), cryptoSuite);
      this.changelogDB = new FileChangelogDB(this, config.getReplicationDBDirectory(), cryptoSuite);
    replSessionSecurity = new ReplSessionSecurity();
    initialize();
      replSessionSecurity = new ReplSessionSecurity();
      initialize();
    }
    catch (ConfigException e)
    {
      // This instance is never returned to the caller, so nothing will ever shut it down:
      // release what the initialization managed to acquire before failing.
      abortInitialization();
      throw e;
    }
    cfg.addChangeListener(this);
    localPorts.add(getReplicationPort());
@@ -441,8 +482,14 @@
    return true;
  }
  /** Initialization function for the replicationServer. */
  private void initialize()
  /**
   * Initialization function for the replicationServer.
   *
   * @throws ConfigException
   *           when the replication server cannot be started, in particular when its listen
   *           port cannot be bound.
   */
  private void initialize() throws ConfigException
  {
    shutdown.set(false);
@@ -451,8 +498,8 @@
      this.changelogDB.initializeDB();
      setServerURL();
      listenSocket = new ServerSocket();
      listenSocket.bind(new InetSocketAddress(getReplicationPort()));
      // Assigned before the threads are created, so that a failure below still releases it.
      listenSocket = bindListenPort(getReplicationPort());
      // creates working threads: we must first connect, then start to listen.
      if (logger.isTraceEnabled())
@@ -467,8 +514,7 @@
        logger.trace("RS " + getMonitorInstanceName() + " creates listen thread");
      }
      listenThread = new ReplicationServerListenThread(this);
      listenThread.start();
      startListenThread(listenSocket);
      if (logger.isTraceEnabled())
      {
@@ -476,14 +522,265 @@
      }
    } catch (UnknownHostException e)
    {
      logger.error(ERR_UNKNOWN_HOSTNAME);
      // Not logged here: the caller reports the ConfigException, logging it once.
      logger.traceException(e);
      throw new ConfigException(ERR_UNKNOWN_HOSTNAME.get(), e);
    } catch (IOException e)
    {
      logger.error(ERR_COULD_NOT_BIND_CHANGELOG, getReplicationPort(), e.getMessage());
      // A replication server whose listen port is not bound is dead: every consumer would
      // otherwise only learn about it as a "connection refused" somewhere else.
      logger.traceException(e);
      throw new ConfigException(bindFailureMessage(getReplicationPort(), e), e);
    }
  }
  /**
   * Binds the given port, retrying a few times when it is momentarily unavailable.
   *
   * @param port
   *          the port to bind
   * @return the bound listen socket
   * @throws IOException
   *           if the port could not be bound within {@link #LISTEN_BIND_ATTEMPTS} attempts
   */
  private ServerSocket bindListenPort(int port) throws IOException
  {
    for (int attempt = 1; ; attempt++)
    {
      final ServerSocket socket = new ServerSocket();
      try
      {
        socket.bind(new InetSocketAddress(port));
        if (attempt > 1)
        {
          logger.info(NOTE_BOUND_CHANGELOG_AFTER_RETRY, port, attempt);
        }
        return socket;
      }
      catch (IOException e)
      {
        close(socket);
        listenPortBindFailures.incrementAndGet();
        if (attempt >= LISTEN_BIND_ATTEMPTS)
        {
          throw e;
        }
        // The port is probed only when giving up: probing it on every attempt would cost a
        // connection to whoever holds it, and delay the next attempt for nothing.
        logger.warn(WARN_RETRYING_BIND_CHANGELOG, port, getExceptionMessage(e), LISTEN_BIND_RETRY_DELAY_MS);
        try
        {
          Thread.sleep(LISTEN_BIND_RETRY_DELAY_MS);
        }
        catch (InterruptedException e2)
        {
          Thread.currentThread().interrupt();
          throw e;
        }
      }
    }
  }
  /**
   * Stops the listen thread and releases the listen port.
   *
   * @throws InterruptedException
   *           if this thread is interrupted while waiting for the listen thread to stop
   */
  private void stopListenThread() throws InterruptedException
  {
    stopListen = true;
    close(listenSocket);
    if (listenThread != null)
    {
      listenThread.join();
      listenThread = null;
    }
  }
  /**
   * Starts a listen thread on the provided listen socket.
   * <p>
   * {@code stopListen} is only cleared here, i.e. once the listen port is bound, so a
   * failure to bind leaves this replication server consistently stopped rather than with a
   * listen thread which would spin on a closed socket.
   *
   * @param boundListenSocket
   *          the bound socket the listen thread will accept connections on
   */
  private void startListenThread(ServerSocket boundListenSocket)
  {
    listenSocket = boundListenSocket;
    stopListen = false;
    listenThread = new ReplicationServerListenThread(this);
    listenThread.start();
  }
  /**
   * Switches the listen port to the one of the provided configuration.
   * <p>
   * The new port is bound while the current one is still open and serving, so a failure
   * leaves this replication server listening on its current port, with its current
   * configuration: there is nothing to roll back, and no window during which this
   * replication server advertises a port that nothing listens to.
   *
   * @param newConfig
   *          the configuration being applied, whose listen port differs from the current one
   * @param ccr
   *          the result of the configuration change, to which a failure is added
   * @return {@code true} when this replication server listens on the new port, in which
   *         case {@code newConfig} has become its configuration
   */
  private boolean switchListenPort(ReplicationServerCfg newConfig, ConfigChangeResult ccr)
  {
    final ReplicationServerCfg previousConfig = this.config;
    final String previousServerURL = serverURL;
    final int newPort = newConfig.getReplicationPort();
    ServerSocket newListenSocket = null;
    try
    {
      // The current listen socket is still open and serving while the new port is bound.
      newListenSocket = bindListenPort(newPort);
      this.config = newConfig;
      setServerURL();
      stopListenThread();
      startListenThread(newListenSocket);
      newListenSocket = null;
      localPorts.remove(previousConfig.getReplicationPort());
      localPorts.add(newPort);
      return true;
    }
    catch (UnknownHostException e)
    {
      logger.traceException(e);
      ccr.setResultCode(ResultCode.OPERATIONS_ERROR);
      ccr.addMessage(ERR_UNKNOWN_HOSTNAME.get());
    }
    catch (IOException e)
    {
      // The new port could not be bound, the current listen socket was left untouched.
      logger.traceException(e);
      ccr.setResultCode(ResultCode.OPERATIONS_ERROR);
      ccr.addMessage(bindFailureMessage(newPort, e));
    }
    catch (InterruptedException e)
    {
      // The previous listen thread may still be running, so do not hand it a new socket:
      // stopListen is still set, which makes that thread stop as soon as it wakes up.
      Thread.currentThread().interrupt();
      logger.traceException(e);
      ccr.setResultCode(ResultCode.OPERATIONS_ERROR);
      ccr.addMessage(ERR_COULD_NOT_STOP_LISTEN_THREAD.get(getExceptionMessage(e)));
    }
    // The failure is reported through the ConfigChangeResult, which the configuration
    // handler logs: nothing of the new configuration was applied.
    this.config = previousConfig;
    serverURL = previousServerURL;
    close(newListenSocket);
    return false;
  }
  /**
   * Returns the message of a failed bind of the listen port, i.e. the error itself plus a
   * best effort diagnostic of what holds the port.
   *
   * @param port
   *          the port which could not be bound
   * @param cause
   *          the error returned by the failed bind
   * @return the message describing the failure
   */
  private static LocalizableMessage bindFailureMessage(int port, IOException cause)
  {
    return new LocalizableMessageBuilder(ERR_COULD_NOT_BIND_CHANGELOG.get(port, getExceptionMessage(cause)))
        .append(" ").append(describeListenPortHolder(port)).toMessage();
  }
  /**
   * Returns a best effort diagnostic of what holds the given port.
   * <p>
   * The port is probed over the loopback interface only, so the diagnostic reports what was
   * observed there and nothing more: a socket bound to another address holds the port
   * without ever accepting a loopback connection, and cannot be told apart from a socket
   * which does not accept connections at all, e.g. a client socket which was given that
   * port as its local port.
   *
   * @param port
   *          the port which could not be bound
   * @return the message describing what was observed on the loopback interface
   */
  private static LocalizableMessage describeListenPortHolder(int port)
  {
    final String probedAddress = InetAddress.getLoopbackAddress().getHostAddress() + ":" + port;
    try (Socket probe = new Socket())
    {
      // Without SO_REUSEADDR a probe which self-connects would, once closed, hold the port
      // in TIME_WAIT and defeat the very bind it is diagnosing.
      probe.setReuseAddress(true);
      probe.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), LISTEN_PORT_PROBE_TIMEOUT_MS);
      if (isSelfConnection(probe))
      {
        // The kernel can only give the probed port to the probe as its local port while
        // that port is free: whoever held it released it in the meantime.
        return ERR_COULD_NOT_BIND_CHANGELOG_PORT_FREE.get(probedAddress);
      }
      return ERR_COULD_NOT_BIND_CHANGELOG_LISTENING.get(probedAddress);
    }
    catch (IOException e)
    {
      logger.traceException(e);
      return ERR_COULD_NOT_BIND_CHANGELOG_NOT_ACCEPTING.get(probedAddress);
    }
  }
  /**
   * Releases what the initialization acquired before it failed.
   * <p>
   * It runs on a partially constructed instance, so every field it uses may still be
   * unassigned.
   */
  private void abortInitialization()
  {
    shutdown.set(true);
    if (connectThread != null)
    {
      connectThread.interrupt();
    }
    close(listenSocket);
    if (listenThread != null)
    {
      listenThread.interrupt();
    }
    shutdownExternalChangelog();
    if (this.changelogDB != null)
    {
      try
      {
        this.changelogDB.shutdownDB();
      }
      catch (ChangelogException ignored)
      {
        logger.traceException(ignored);
      }
    }
  }
  /**
   * Indicates whether this replication server has bound its listen port, i.e. whether it
   * can accept connections from directory servers and from other replication servers.
   *
   * @return {@code true} if this replication server is listening
   */
  public boolean isListening()
  {
    final ServerSocket socket = listenSocket;
    return socket != null && socket.isBound() && !socket.isClosed();
  }
  /**
   * Enable the external changelog if it is not already enabled.
   * <p>
   * The external changelog is provided by the changelog backend.
@@ -515,6 +812,9 @@
            getExceptionMessage(e)));
      }
      // Set before the rules are registered, so that a partial registration is released
      // too: this instance is then the one which registered whatever is registered.
      externalChangelogRegistered = true;
      registerVirtualAttributeRules();
    }
    catch (Exception e)
@@ -535,7 +835,14 @@
      changelogBackend.finalizeBackend();
      changelogBackend = null;
    }
    deregisterVirtualAttributeRules();
    if (externalChangelogRegistered)
    {
      // Virtual attribute rules are registered globally, by attribute name: deregistering
      // rules which this instance did not register would strip them from the instance
      // which did, e.g. when this one took the early return of enableExternalChangeLog().
      externalChangelogRegistered = false;
      deregisterVirtualAttributeRules();
    }
  }
  private List<VirtualAttributeRule> getVirtualAttributesRules() throws DirectoryException
@@ -746,7 +1053,7 @@
    {
      ReplicationServerDomain domain = baseDNs.get(baseDN);
      if (domain == null && create) {
        domain = new ReplicationServerDomain(baseDN, this);
        domain = new ReplicationServerDomain(baseDN, this).start();
        baseDNs.put(baseDN, domain);
      }
      return domain;
@@ -770,7 +1077,7 @@
      }
      // Wake up connect thread.
      connectThreadLock.notify();
      connectThreadLock.notifyAll();
    }
    // Wait until the connect thread has processed next connect phase.
@@ -863,17 +1170,14 @@
  {
    int port = configuration.getReplicationPort();
    try
    try (ServerSocket tmpSocket = new ServerSocket())
    {
      ServerSocket tmpSocket = new ServerSocket();
      tmpSocket.bind(new InetSocketAddress(port));
      tmpSocket.close();
      return true;
    }
    catch (Exception e)
    {
      LocalizableMessage message = ERR_COULD_NOT_BIND_CHANGELOG.get(port, e.getMessage());
      unacceptableReasons.add(message);
      unacceptableReasons.add(ERR_COULD_NOT_BIND_CHANGELOG.get(port, getExceptionMessage(e)));
      return false;
    }
  }
@@ -884,11 +1188,21 @@
  {
    final ConfigChangeResult ccr = new ConfigChangeResult();
    final Set<HostPort> oldRSAddresses = getConfiguredRSAddresses();
    final ReplicationServerCfg oldConfig = this.config;
    // Changing the listen port requires to stop the listen thread and restart it. It is
    // done first, and the new port is bound before the current one is released, so that a
    // change which cannot be applied leaves this replication server as it was, instead of
    // half configured and, worse, without any listener.
    if (configuration.getReplicationPort() != oldConfig.getReplicationPort()
        && !switchListenPort(configuration, ccr))
    {
      return ccr;
    }
    // Some of those properties change don't need specific code.
    // They will be applied for next connections. Some others have immediate effect
    final Set<HostPort> oldRSAddresses = getConfiguredRSAddresses();
    final ReplicationServerCfg oldConfig = this.config;
    this.config = configuration;
    disconnectRemovedReplicationServers(oldRSAddresses);
@@ -915,39 +1229,6 @@
    cryptoSuite.newParameters(config.getCipherTransformation(), config.getCipherKeyLength(),
        config.isConfidentialityEnabled());
    // changing the listen port requires to stop the listen thread
    // and restart it.
    if (getReplicationPort() != oldConfig.getReplicationPort())
    {
      stopListen = true;
      try
      {
        close(listenSocket);
        if (listenThread != null)
        {
          listenThread.join();
        }
        stopListen = false;
        setServerURL();
        listenSocket = new ServerSocket();
        listenSocket.bind(new InetSocketAddress(getReplicationPort()));
        listenThread = new ReplicationServerListenThread(this);
        listenThread.start();
      }
      catch (IOException e)
      {
        logger.traceException(e);
        logger.error(ERR_COULD_NOT_CLOSE_THE_SOCKET, e);
      }
      catch (InterruptedException e)
      {
        logger.traceException(e);
        logger.error(ERR_COULD_NOT_STOP_LISTEN_THREAD, e);
      }
    }
    // Update period value for monitoring publishers
    if (oldConfig.getMonitoringPeriod() != config.getMonitoringPeriod())
    {
@@ -1031,7 +1312,13 @@
  public boolean isConfigurationChangeAcceptable(
      ReplicationServerCfg configuration, List<LocalizableMessage> unacceptableReasons)
  {
    return true;
    if (configuration.getReplicationPort() == getReplicationPort())
    {
      return true;
    }
    // The change is persisted before it is applied, so rejecting a port which cannot be
    // bound is the only way to keep the configuration and the listen port in sync.
    return isConfigurationAcceptable(configuration, unacceptableReasons);
  }
  /**
opendj-server-legacy/src/main/java/org/opends/server/replication/server/ReplicationServerDomain.java
@@ -283,11 +283,24 @@
    this.domainDB =
        localReplicationServer.getChangelogDB().getReplicationDomainDB();
    this.statusAnalyzer = new StatusAnalyzer(this);
    this.statusAnalyzer.start();
    DirectoryServer.registerMonitorProvider(this);
  }
  /**
   * Starts the status analyzer of this domain.
   * <p>
   * The analyzer is not started by the constructor so that {@code this} is not published to its
   * thread before construction has completed.
   *
   * @return this domain
   */
  ReplicationServerDomain start()
  {
    statusAnalyzer.start();
    return this;
  }
  /**
   * Add an update that has been received to the list of
   * updates that must be forwarded to all other servers.
   *
opendj-server-legacy/src/main/java/org/opends/server/replication/server/ServerHandler.java
@@ -791,7 +791,7 @@
    final Random random = new Random();
    final int randomTime = random.nextInt(6); // Random from 0 to 5
    // Wait at least 3 seconds + (0 to 5 seconds)
    final long timeout = 3000 + randomTime * 1000;
    final long timeout = 3000 + randomTime * 1000L;
    final boolean lockAcquired = replicationServerDomain.tryLock(timeout);
    if (!lockAcquired)
    {
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/ChangeNumberIndexer.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.server.changelog.file;
@@ -221,7 +222,7 @@
    {
      synchronized (this)
      {
        notify();
        notifyAll();
      }
    }
  }
@@ -364,7 +365,7 @@
    super.initiateShutdown();
    synchronized (this)
    {
      notify();
      notifyAll();
    }
  }
@@ -590,7 +591,7 @@
      // wait until clear() has been done by thread, always waking it up
      synchronized (this)
      {
        notify();
        notifyAll();
      }
      // ensures thread wait that this thread's state is cleaned up
      Thread.yield();
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/FileChangelogDB.java
@@ -593,7 +593,7 @@
      final ChangelogDBPurger currentPurger = cnPurger.get();
      synchronized (currentPurger)
      {
        currentPurger.notify();
        currentPurger.notifyAll();
      }
    }
  }
@@ -1017,7 +1017,7 @@
      super.initiateShutdown();
      synchronized (this)
      {
        notify(); // wake up the purger thread for faster shutdown
        notifyAll(); // wake up the purger thread for faster shutdown
      }
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/replication/server/changelog/file/LogFile.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.server.changelog.file;
@@ -112,8 +113,18 @@
    if (isWriteEnabled)
    {
      ensureLogFileIsValid(parser);
      writer = BlockLogWriter.newWriter(new LogWriter(logfile), parser);
      initializeNewestRecord();
      final LogWriter logWriter = new LogWriter(logfile);
      try
      {
        writer = BlockLogWriter.newWriter(logWriter, parser);
        initializeNewestRecord();
      }
      catch (ChangelogException | RuntimeException e)
      {
        // The writer is not reachable from a half-constructed log file, so close it here.
        StaticUtils.close(logWriter);
        throw e;
      }
    }
    else
    {
opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationBroker.java
@@ -829,7 +829,7 @@
      final ConnectedRS rs = connectedRS.get();
      if (rs.isConnected())
      {
        connectPhaseLock.notify();
        connectPhaseLock.notifyAll();
        final long rsGenId = rs.rsInfo.getGenerationId();
        final int rsServerId = rs.rsInfo.getServerId();
@@ -851,7 +851,7 @@
        if (!connectionError)
        {
          connectionError = true;
          connectPhaseLock.notify();
          connectPhaseLock.notifyAll();
          if (!rsInfos.isEmpty())
          {
@@ -1341,7 +1341,7 @@
    {
      if (foundBestRS())
      {
        final Integer bestRSServerId = getBestRS().getServerId();
        final int bestRSServerId = getBestRS().getServerId();
        if (rsEvals.get(bestRSServerId) == null)
        {
          final LocalizableMessage eval = NOTE_BEST_RS.get(bestRSServerId, localServerId);
@@ -2510,7 +2510,7 @@
          synchronized (monitorResponse)
          {
            monitorResponse.set(true);
            monitorResponse.notify();
            monitorResponse.notifyAll();
          }
          // Update the replication servers ServerStates with new received info
opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java
@@ -953,7 +953,7 @@
    {
      synchronized (update)
      {
        update.notify();
        update.notifyAll();
      }
      // Analyze status of embedded in the ack to see if everything went well
opendj-server-legacy/src/main/java/org/opends/server/schema/GeneralizedTimeSyntax.java
@@ -14,6 +14,7 @@
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2009 D. J. Hagberg, Millibits Consulting, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.schema;
@@ -1199,7 +1200,7 @@
              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
    }
    Double fractionValue = Double.parseDouble(fractionBuffer.toString());
    double fractionValue = Double.parseDouble(fractionBuffer.toString());
    long additionalMilliseconds = Math.round(fractionValue * multiplier);
    try
opendj-server-legacy/src/main/java/org/opends/server/tasks/ImportTask.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.tasks;
@@ -327,6 +328,15 @@
      }
    }
    if (backend == null)
    {
      // Unreachable as long as the checks above hold: a null backend ID implies at least one
      // include branch, and every include branch either resolves to a backend or is rejected.
      // The guard mirrors the one in runTask() and keeps the dereference below safe.
      LocalizableMessage message = ERR_LDIFIMPORT_NO_BACKENDS_FOR_ID.get();
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    // Make sure the selected backend will handle all the include branches
    defaultIncludeBranches = new ArrayList<>(backend.getBaseDNs());
@@ -475,6 +485,15 @@
      }
    }
    if (backend == null)
    {
      // Unlike initializeTask(), this method does not reject an include branch which resolves to no
      // backend, so none of them having resolved leaves nothing to import into. The message of the
      // backend ID case is reused here rather than adding a new one for a malformed task.
      logger.error(ERR_LDIFIMPORT_NO_BACKENDS_FOR_ID);
      return TaskState.STOPPED_BY_ERROR;
    }
    // Find backends with subordinate base DNs that should be excluded from the import.
    defaultIncludeBranches = new HashSet<>(backend.getBaseDNs());
    for (Backend<?> subBackend : getServerContext().getBackendConfigManager().getSubordinateBackends(backend))
opendj-server-legacy/src/main/java/org/opends/server/tasks/InitializeTask.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.tasks;
@@ -42,7 +43,15 @@
  private String domainString;
  private int  source;
  private LDAPReplicationDomain domain;
  private TaskState initState;
  /**
   * The monitor used to signal the completion of the import to {@link #runTask()}.
   * <p>
   * A dedicated lock is required because {@link #initState} is reassigned: synchronizing on the
   * state itself would lock the monitor of an enum constant which is both shared JVM wide and
   * different for the waiting and the notifying thread.
   */
  private final Object initStateLock = new Object();
  private volatile TaskState initState;
  /** The total number of entries expected to be processed when this import will end successfully. */
  private long total;
@@ -103,12 +112,12 @@
      // launch the import
      domain.initializeFromRemote(source, this);
      synchronized(initState)
      synchronized (initStateLock)
      {
        // Waiting for the end of the job
        while (initState == TaskState.RUNNING)
        {
          initState.wait(1000);
          initStateLock.wait(1000);
          replaceAttributeValue(ATTR_TASK_INITIALIZE_LEFT, String.valueOf(left));
          replaceAttributeValue(ATTR_TASK_INITIALIZE_DONE, String.valueOf(total-left));
        }
@@ -158,9 +167,9 @@
    finally
    {
      // Wake up runTask method waiting for completion
      synchronized (initState)
      synchronized (initStateLock)
      {
        initState.notify();
        initStateLock.notifyAll();
      }
    }
  }
opendj-server-legacy/src/main/java/org/opends/server/tasks/PurgeConflictsHistoricalTask.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.tasks;
@@ -123,18 +124,20 @@
  @Override
  protected TaskState runTask()
  {
    Boolean purgeCompletedInTime = false;
    boolean purgeCompletedInTime = false;
    logger.trace("PurgeConflictsHistoricalTask is starting on domain: %s max duration (sec): %d",
        domain.getBaseDN(), purgeTaskMaxDurationInSec);
    try
    {
      replaceAttributeValue(ATTR_TASK_CONFLICTS_HIST_PURGE_COMPLETED_IN_TIME, purgeCompletedInTime.toString());
      replaceAttributeValue(ATTR_TASK_CONFLICTS_HIST_PURGE_COMPLETED_IN_TIME,
          String.valueOf(purgeCompletedInTime));
      // launch the task
      domain.purgeConflictsHistorical(this, TimeThread.getTime() + purgeTaskMaxDurationInSec*1000);
      domain.purgeConflictsHistorical(this, TimeThread.getTime() + purgeTaskMaxDurationInSec * 1000L);
      purgeCompletedInTime = true;
      replaceAttributeValue(ATTR_TASK_CONFLICTS_HIST_PURGE_COMPLETED_IN_TIME, purgeCompletedInTime.toString());
      replaceAttributeValue(ATTR_TASK_CONFLICTS_HIST_PURGE_COMPLETED_IN_TIME,
          String.valueOf(purgeCompletedInTime));
      initState =  TaskState.COMPLETED_SUCCESSFULLY;
    }
opendj-server-legacy/src/main/java/org/opends/server/tools/ConfigureWindowsService.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.tools;
@@ -270,25 +271,27 @@
    {
      String serviceName = null;
      Process p = Runtime.getRuntime().exec(cmd);
      BufferedReader stdout = new BufferedReader(new InputStreamReader(p.getInputStream()));
      boolean processDone = false;
      String s;
      while (!processDone)
      try (BufferedReader stdout = new BufferedReader(new InputStreamReader(p.getInputStream())))
      {
        try
        boolean processDone = false;
        String s;
        while (!processDone)
        {
          p.exitValue();
          processDone = true;
        }
        catch (Throwable t)
        {
        }
        while ((s = stdout.readLine()) != null)
        {
          serviceName = s;
          if (serviceName.trim().length() == 0)
          try
          {
            serviceName = null;
            p.exitValue();
            processDone = true;
          }
          catch (Throwable t)
          {
          }
          while ((s = stdout.readLine()) != null)
          {
            serviceName = s;
            if (serviceName.trim().length() == 0)
            {
              serviceName = null;
            }
          }
        }
      }
@@ -608,25 +611,26 @@
    {
      int resultCode = -1;
      Process process = new ProcessBuilder(cmd).start();
      BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
      boolean processDone = false;
      String s;
      while (!processDone)
      try (BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())))
      {
        try
        boolean processDone = false;
        String s;
        while (!processDone)
        {
          resultCode = process.exitValue();
          processDone = true;
        }
        catch (Throwable t)
        {
        }
        while ((s = stdout.readLine()) != null)
        {
          if (s.trim().length() != 0)
          try
          {
            serviceName = s;
            resultCode = process.exitValue();
            processDone = true;
          }
          catch (Throwable t)
          {
          }
          while ((s = stdout.readLine()) != null)
          {
            if (s.trim().length() != 0)
            {
              serviceName = s;
            }
          }
        }
      }
opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnection.java
@@ -348,6 +348,9 @@
              case CHANGE_AFTER_RESET:
                out.println(INFO_BIND_MUST_CHANGE_PASSWORD.get());
                break;
              default:
                // No action needed for the remaining values.
                break;
            }
          }
opendj-server-legacy/src/main/java/org/opends/server/tools/dsreplication/ReplicationCliMain.java
@@ -6595,7 +6595,7 @@
              ERR_REPLICATION_INITIALIZING_TRIES_COMPLETED.get(
                  pnfe.getMessageObject()), INITIALIZING_TRIES_COMPLETED, pnfe);
        }
        sleepCatchInterrupt((5 - nTries) * 3000);
        sleepCatchInterrupt((5 - nTries) * 3000L);
      }
      catch (ApplicationException ae)
      {
@@ -6648,7 +6648,7 @@
              ERR_REPLICATION_INITIALIZING_TRIES_COMPLETED.get(
                  pnfe.getMessageObject()), INITIALIZING_TRIES_COMPLETED, pnfe);
        }
        sleepCatchInterrupt((5 - nTries) * 3000);
        sleepCatchInterrupt((5 - nTries) * 3000L);
      }
      catch (ClientException ae)
      {
opendj-server-legacy/src/main/java/org/opends/server/tools/makeldif/MakeLDIFInputStream.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2015-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.tools.makeldif;
@@ -65,13 +66,16 @@
  /** The queue used to hold generated entries until they can be read. */
  private LinkedBlockingQueue<TemplateEntry> entryQueue;
  /** The background thread used to actually generate the entries. */
  private final MakeLDIFInputStreamThread generatorThread;
  /**
   * Creates a new MakeLDIF input stream that will generate entries based on the
   * provided template file.
   *
   * @param  templateFile  The template file to use to generate the entries.
   */
  public MakeLDIFInputStream(TemplateFile templateFile)
  private MakeLDIFInputStream(TemplateFile templateFile)
  {
    allGenerated = false;
    closed       = false;
@@ -93,7 +97,25 @@
    }
    /* The background thread being used to actually generate the entries. */
    new MakeLDIFInputStreamThread(this, templateFile).start();
    generatorThread = new MakeLDIFInputStreamThread(this, templateFile);
  }
  /**
   * Creates a new MakeLDIF input stream based on the provided template file and starts generating
   * entries in the background.
   * <p>
   * The generator thread is started here rather than by the constructor so that {@code this} is not
   * published to it before construction has completed.
   *
   * @param  templateFile  The template file to use to generate the entries.
   *
   * @return  A new input stream which has started generating entries.
   */
  public static MakeLDIFInputStream newStartedInputStream(TemplateFile templateFile)
  {
    MakeLDIFInputStream inputStream = new MakeLDIFInputStream(templateFile);
    inputStream.generatorThread.start();
    return inputStream;
  }
opendj-server-legacy/src/main/java/org/opends/server/tools/status/StatusCli.java
@@ -79,7 +79,6 @@
import org.opends.server.types.InitializationException;
import org.opends.server.types.NullOutputStream;
import org.opends.server.util.BuildVersion;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
import com.forgerock.opendj.cli.ArgumentException;
@@ -355,9 +354,10 @@
    writeStatus(controlInfo.getServerDescriptor());
    int period = argParser.getRefreshPeriod();
    boolean first = true;
    while (period > 0)
    // A positive refresh period means refreshing until the command is interrupted.
    while (period > 0 && !Thread.currentThread().isInterrupted())
    {
      long timeToSleep = period * 1000;
      long timeToSleep = period * 1000L;
      if (!first)
      {
        long t1 = System.currentTimeMillis();
@@ -369,7 +369,17 @@
      if (timeToSleep > 0)
      {
        StaticUtils.sleep(timeToSleep);
        try
        {
          // Not StaticUtils.sleep(): it discards the interruption, which would leave this loop
          // running with no way for the caller to stop it.
          Thread.sleep(timeToSleep);
        }
        catch (InterruptedException e)
        {
          Thread.currentThread().interrupt();
          break;
        }
      }
      println();
      println(LocalizableMessage.raw("          ---------------------"));
@@ -1125,7 +1135,7 @@
    // Interact with the user though the console to get
    // LDAP connection information
    final String hostName = getHostNameForLdapUrl(ci.getHostName());
    final Integer portNumber = ci.getPortNumber();
    final int portNumber = ci.getPortNumber();
    final DN bindDN = ci.getBindDN();
    final String bindPassword = ci.getBindPassword();
    TrustManager trustManager = ci.getTrustManager();
opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeUtils.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.tools.upgrade;
@@ -54,6 +55,7 @@
import org.forgerock.opendj.ldif.LDIFEntryReader;
import org.forgerock.opendj.ldif.LDIFEntryWriter;
import org.forgerock.util.Reject;
import org.forgerock.util.Utils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.util.ChangeOperationType;
import org.opends.server.util.SchemaUtils;
@@ -304,8 +306,18 @@
  {
    final Schema schema = getUpgradeSchema();
    final File configFile = new File(configDirectory, CURRENT_CONFIG_FILE_NAME);
    final LDIFEntryReader entryReader = new LDIFEntryReader(new FileInputStream(configFile)).setSchema(schema);
    return LDIF.search(entryReader, searchRequest, schema);
    final FileInputStream configStream = new FileInputStream(configFile);
    try
    {
      // Ownership of the stream passes to the returned reader, which the caller must close.
      final LDIFEntryReader entryReader = new LDIFEntryReader(configStream).setSchema(schema);
      return LDIF.search(entryReader, searchRequest, schema);
    }
    catch (RuntimeException e)
    {
      Utils.closeSilently(configStream);
      throw e;
    }
  }
  /**
@@ -333,8 +345,10 @@
    int changeCount = 0;
    final Schema schema = getUpgradeSchema();
    try (LDIFEntryReader entryReader = new LDIFEntryReader(new FileInputStream(configFile)).setSchema(schema);
        LDIFEntryWriter writer = new LDIFEntryWriter(new FileOutputStream(copyConfig)))
    try (FileInputStream configStream = new FileInputStream(configFile);
        LDIFEntryReader entryReader = new LDIFEntryReader(configStream).setSchema(schema);
        FileOutputStream copyStream = new FileOutputStream(copyConfig);
        LDIFEntryWriter writer = new LDIFEntryWriter(copyStream))
    {
      writer.setWrapColumn(80);
@@ -361,6 +375,9 @@
            ldifDNs.add(DN.valueOf(removeDnPrefix(dnLine)));
          }
          break;
        default:
          // No action needed for the remaining values.
          break;
        }
      }
opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java
@@ -13,7 +13,7 @@
 *
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2023-2024 3A Systems, LLC.
 * Portions Copyright 2023-2026 3A Systems, LLC.
 */
package org.opends.server.types;
@@ -3357,7 +3357,7 @@
    {
      // The first byte must be the entry version.  If it's not one
      // we recognize, then that's an error.
      Byte version = entryBuffer.readByte();
      byte version = entryBuffer.readByte();
      if (version != 0x03 && version != 0x02 && version != 0x01)
      {
        LocalizableMessage message = ERR_ENTRY_DECODE_UNRECOGNIZED_VERSION.get(
opendj-server-legacy/src/main/java/org/opends/server/types/LDAPURL.java
@@ -1480,6 +1480,9 @@
      case SUBORDINATES:
        buffer.append("subordinate");
        break;
      default:
        // No action needed for the remaining values.
        break;
    }
    buffer.append("?");
opendj-server-legacy/src/main/java/org/opends/server/types/LDIFImportConfig.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.types;
@@ -203,7 +204,7 @@
   */
  public LDIFImportConfig(TemplateFile templateFile)
  {
    this(new MakeLDIFInputStream(templateFile));
    this(MakeLDIFInputStream.newStartedInputStream(templateFile));
  }
@@ -243,7 +244,7 @@
      if (isCompressed)
      {
        inputStream = new GZIPInputStream(inputStream);
        inputStream = wrapWithGzip(inputStream);
      }
      reader = new BufferedReader(new InputStreamReader(inputStream),
@@ -253,6 +254,23 @@
    return reader;
  }
  /**
   * Wraps the provided stream for decompression, closing it if the wrapping fails so that the
   * underlying file descriptor is not leaked.
   */
  private static InputStream wrapWithGzip(final InputStream inputStream) throws IOException
  {
    try
    {
      return new GZIPInputStream(inputStream);
    }
    catch (IOException | RuntimeException e)
    {
      StaticUtils.close(inputStream);
      throw e;
    }
  }
  /**
@@ -284,7 +302,7 @@
    if (isCompressed)
    {
      inputStream = new GZIPInputStream(inputStream);
      inputStream = wrapWithGzip(inputStream);
    }
    reader = new BufferedReader(new InputStreamReader(inputStream), bufferSize);
opendj-server-legacy/src/main/java/org/opends/server/types/RecordingInputStream.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2014-2015 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.types;
@@ -99,13 +100,13 @@
  /** {@inheritDoc} */
  @Override
  public void mark(int i) {
  public synchronized void mark(int i) {
    parentStream.mark(i);
  }
  /** {@inheritDoc} */
  @Override
  public void reset() throws IOException {
  public synchronized void reset() throws IOException {
    parentStream.reset();
  }
opendj-server-legacy/src/main/java/org/opends/server/util/CronExecutorService.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.util;
@@ -90,8 +91,11 @@
    @Override
    public boolean cancel(boolean mayInterruptIfRunning)
    {
      return schedulerFuture.cancel(mayInterruptIfRunning)
          | (executorFuture != null && executorFuture.cancel(mayInterruptIfRunning));
      // Both futures must be cancelled, so neither call may be short circuited.
      final boolean schedulerCancelled = schedulerFuture.cancel(mayInterruptIfRunning);
      final boolean executorCancelled =
          executorFuture != null && executorFuture.cancel(mayInterruptIfRunning);
      return schedulerCancelled || executorCancelled;
    }
    @Override
@@ -351,7 +355,10 @@
  @Override
  public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException
  {
    return cronScheduler.awaitTermination(timeout, unit) & cronExecutorService.awaitTermination(timeout, unit);
    // Both services must be awaited, so neither call may be short circuited.
    final boolean schedulerTerminated = cronScheduler.awaitTermination(timeout, unit);
    final boolean executorTerminated = cronExecutorService.awaitTermination(timeout, unit);
    return schedulerTerminated && executorTerminated;
  }
  @Override
opendj-server-legacy/src/main/java/org/opends/server/util/StaticUtils.java
@@ -511,11 +511,7 @@
      int firstByte = 0xFF & a[i];
      int secondByte = 0xFF & a2[i];
      if (firstByte != secondByte) {
        if (firstByte < secondByte) {
          return -1;
        } else if (firstByte > secondByte) {
          return 1;
        }
        return firstByte < secondByte ? -1 : 1;
      }
    }
@@ -1795,7 +1791,10 @@
        }
      }
      return successful & file.delete();
      // The delete must be attempted even when a child could not be removed, so it is
      // evaluated into a local before being combined with the running result.
      final boolean deleted = file.delete();
      return successful && deleted;
    }
    return false;
  }
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -14,6 +14,7 @@
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2024-2025 3A Systems,LLC.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.workflowelement.localbackend;
@@ -880,6 +881,10 @@
                    ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
              case WARN:
                logger.error(message);
                break;
              default:
                // No action needed for the remaining values.
                break;
              }
            }
          }
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 * Portions copyright 2024-2025 3A Systems,LLC.
 */
package org.opends.server.workflowelement.localbackend;
@@ -738,6 +739,9 @@
        case INCREMENT:
          newEntry.incrementAttribute(a);
          break;
        default:
          // No action needed for the remaining values.
          break;
      }
    }
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -14,6 +14,7 @@
 * Copyright 2008-2011 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2024-2025 3A Systems,LLC.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.workflowelement.localbackend;
@@ -951,6 +952,9 @@
    case INCREMENT:
      processIncrementModification(attr);
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
  }
@@ -1235,6 +1239,9 @@
          logger.error(msg);
          invalidReason = new LocalizableMessageBuilder();
          break;
        default:
          // No action needed for the remaining values.
          break;
        }
      }
    }
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendWorkflowElement.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2008-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.workflowelement.localbackend;
@@ -711,6 +712,10 @@
      {
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, errorMsg.get(entryDN));
      }
      break;
    default:
      // No action needed for the remaining values.
      break;
    }
  }
opendj-server-legacy/src/messages/org/opends/messages/protocol.properties
@@ -729,6 +729,10 @@
 use request controls
ERR_CONNHANDLER_CANNOT_BIND_432=The %s connection handler \
 defined in configuration entry %s was unable to bind to %s:%d: %s
ERR_CONNHANDLER_TIMEOUT_WAITING_FOR_LISTENER_1540=The %s did not attempt to \
 open its listen port within %d seconds. The Directory Server will stop \
 waiting for it and continue, but that connection handler may never accept \
 connections
ERR_JMX_SEARCH_INSUFFICIENT_PRIVILEGES_438=You do not have sufficient \
 privileges to perform search operations through JMX
ERR_JMX_INSUFFICIENT_PRIVILEGES_439=You do not have sufficient \
opendj-server-legacy/src/messages/org/opends/messages/replication.properties
@@ -12,6 +12,7 @@
#
# Copyright 2006-2010 Sun Microsystems, Inc.
# Portions Copyright 2011-2016 ForgeRock AS.
# Portions Copyright 2026 3A Systems, LLC.
# This file contains the primary Directory Server configuration.  It must not
# be directly edited while the server is online.  The server configuration
@@ -598,3 +599,12 @@
 in domain "%s" from this directory server DS(%d): the remote directory server DS(%d) is unknown
ERR_REPLICATION_UNEXPECTED_MESSAGE_300=New replication connection from %s started with unexpected message %s and is \
 being closed
WARN_RETRYING_BIND_CHANGELOG_301=Replication Server could not bind to the listen port : %d. \
 Error : %s. Retrying in %d ms
NOTE_BOUND_CHANGELOG_AFTER_RETRY_302=Replication Server bound to the listen port : %d \
 at attempt %d
ERR_COULD_NOT_BIND_CHANGELOG_LISTENING_303=Another socket is listening on %s
ERR_COULD_NOT_BIND_CHANGELOG_NOT_ACCEPTING_304=Nothing accepted a connection on %s : the port is held \
 either by a socket bound to another address, or by a socket which does not accept connections
ERR_COULD_NOT_BIND_CHANGELOG_PORT_FREE_305=Nothing holds %s anymore : the port was released after the \
 last attempt to bind it
opendj-server-legacy/src/snmp/src/org/opends/server/snmp/DsMIBImpl.java
@@ -238,8 +238,7 @@
              this.mib, this.server, this.applIndex);
      // if the entry already exists nothing to do
      if ((this.dsTableEntries.containsKey(entry.getObjectName())) ||
              (entry == null)) {
      if (this.dsTableEntries.containsKey(entry.getObjectName())) {
        return true;
      }
@@ -279,8 +278,7 @@
              this.applIndex, this.applIfOpsIndex);
      // If the entry already exists then nothing to do
      if ((this.dsApplIfOpsTableEntries.containsKey(entry.getObjectName())) ||
              (entry == null)) {
      if (this.dsApplIfOpsTableEntries.containsKey(entry.getObjectName())) {
        return true;
      }
      // Add the entry in the Table
opendj-server-legacy/src/test/java/org/opends/server/TestCaseUtils.java
@@ -51,6 +51,7 @@
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.BindException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
@@ -842,6 +843,62 @@
  }
  /**
   * Asserts that the given local port accepts connections right now, without any retry loop.
   * <p>
   * Intended for connection handler tests: a handler start method must not return before its listen
   * port is open. When the port is closed this method tells the two possible causes apart, because a
   * plain connection failure cannot distinguish them: the handler may never have managed to bind at
   * all, or its start method may have returned too early.
   *
   * @param port
   *          the port the connection handler under test is supposed to be listening on
   * @throws IOException
   *           if the connection fails for a reason other than the port being closed
   */
  public static void assertPortIsAcceptingConnections(int port) throws IOException
  {
    try
    {
      connectTo(port);
    }
    catch (ConnectException e)
    {
      // Give the handler thread the extra time it should not have needed. If the port opens now, the
      // handler can bind and the start method simply returned too early, which is the regression this
      // check is about. If it never opens, the test failed for an unrelated reason.
      assertTrue(waitForPortToOpen(port), "the connection handler never opened port " + port);
      fail("the connection handler start method returned before port " + port + " was open");
    }
  }
  private static void connectTo(int port) throws IOException
  {
    try (Socket socket = new Socket())
    {
      socket.connect(new InetSocketAddress("127.0.0.1", port), 1000);
    }
  }
  private static boolean waitForPortToOpen(int port)
  {
    final long deadline = System.currentTimeMillis() + 10000;
    do
    {
      try
      {
        connectTo(port);
        return true;
      }
      catch (IOException ignored)
      {
        sleep(100);
      }
    }
    while (System.currentTimeMillis() < deadline);
    return false;
  }
  /**
   * Finds a free server socket port on the local host.
   *
   * @return The free port.
opendj-server-legacy/src/test/java/org/opends/server/backends/jeb/JebTestCase.java
@@ -13,6 +13,7 @@
 *
 * Copyright 2006-2008 Sun Microsystems, Inc.
 * Portions Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.backends.jeb;
@@ -65,7 +66,7 @@
        ArrayList<LocalizableMessage> warnings = new ArrayList<>();
        templateFile.parse(template, warnings);
        MakeLDIFInputStream ldifEntryStream =
            new MakeLDIFInputStream(templateFile);
            MakeLDIFInputStream.newStartedInputStream(templateFile);
        LDIFReader reader =
            new LDIFReader(new LDIFImportConfig(ldifEntryStream));
        for(int i =0; i<numEntries;i++) {
opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java
New file
@@ -0,0 +1,97 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.protocols.http;
import static org.testng.Assert.*;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.server.config.meta.HTTPConnectionHandlerCfgDefn;
import org.forgerock.opendj.server.config.server.HTTPConnectionHandlerCfg;
import org.opends.server.DirectoryServerTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.extensions.InitializationUtils;
import org.opends.server.types.Entry;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@SuppressWarnings("javadoc")
@Test(groups = { "precommit", "http" }, sequential = true)
public class HTTPConnectionHandlerTestCase extends DirectoryServerTestCase
{
  private static final LocalizableMessage STOP_REASON = LocalizableMessage.raw("Don't need a reason.");
  @BeforeClass
  public void setUp() throws Exception
  {
    // This test suite depends on having the schema available, so we'll start the server.
    TestCaseUtils.startServer();
  }
  /**
   * The start method must not return before the handler thread has attempted to start the embedded
   * HTTP server, otherwise a client connecting right after the handler has been enabled through
   * dsconfig can be refused.
   *
   * @throws Exception
   *           if the handler cannot be instantiated or started.
   */
  @Test
  public void testStartWaitsForListenPort() throws Exception
  {
    final int listenPort = TestCaseUtils.findFreePort();
    Entry handlerEntry = TestCaseUtils.makeEntry(
        "dn: cn=HTTP Connection Handler,cn=Connection Handlers,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-connection-handler",
        "objectClass: ds-cfg-http-connection-handler",
        "cn: HTTP Connection Handler",
        "ds-cfg-java-class: org.opends.server.protocols.http.HTTPConnectionHandler",
        "ds-cfg-enabled: true",
        "ds-cfg-listen-address: 127.0.0.1",
        "ds-cfg-listen-port: " + listenPort,
        "ds-cfg-accept-backlog: 128",
        "ds-cfg-keep-stats: false",
        "ds-cfg-use-tcp-keep-alive: true",
        "ds-cfg-use-tcp-no-delay: true",
        "ds-cfg-allow-tcp-reuse-address: true",
        "ds-cfg-max-request-size: 5 megabytes",
        "ds-cfg-buffer-size: 4096 bytes",
        "ds-cfg-max-blocked-write-time-limit: 2 minutes",
        "ds-cfg-use-ssl: false",
        "ds-cfg-ssl-client-auth-policy: optional",
        "ds-cfg-ssl-cert-nickname: server-cert");
    HTTPConnectionHandlerCfg config =
        InitializationUtils.getConfiguration(HTTPConnectionHandlerCfgDefn.getInstance(), handlerEntry);
    HTTPConnectionHandler handler = new HTTPConnectionHandler();
    handler.initializeConnectionHandler(DirectoryServer.getInstance().getServerContext(), config);
    try
    {
      handler.start();
      // No retry loop here on purpose: once start() has returned, the port must already be open.
      TestCaseUtils.assertPortIsAcceptingConnections(listenPort);
    }
    finally
    {
      handler.processServerShutdown(STOP_REASON);
      handler.finalizeConnectionHandler(STOP_REASON);
      handler.join(10000);
      assertFalse(handler.isAlive(), "the connection handler thread is still running");
    }
  }
}
opendj-server-legacy/src/test/java/org/opends/server/protocols/ldap/TestLDAPConnectionHandler.java
@@ -13,7 +13,7 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2012-2016 ForgeRock AS.
 * Portions Copyright 2025 3A Systems, LLC.
 * Portions Copyright 2025-2026 3A Systems, LLC.
 */
package org.opends.server.protocols.ldap;
@@ -127,6 +127,57 @@
  }
  /**
   * The start method must not return before the handler thread has attempted to
   * open the listen port, otherwise a client connecting right after the server
   * startup can be refused.
   *
   * @throws Exception if the handler cannot be instantiated or started.
   */
  @Test
  public void testStartWaitsForListenPort() throws Exception {
    Entry handlerEntry = TestCaseUtils.makeEntry(
        "dn: cn=LDAP Connection Handler,cn=Connection Handlers,cn=config",
        "objectClass: top",
        "objectClass: ds-cfg-connection-handler",
        "objectClass: ds-cfg-ldap-connection-handler",
        "cn: LDAP Connection Handler",
        "ds-cfg-java-class: org.forgerock.opendj.reactive.LDAPConnectionHandler2",
        "ds-cfg-enabled: true",
        "ds-cfg-listen-address: 127.0.0.1",
        "ds-cfg-accept-backlog: 128",
        "ds-cfg-allow-ldap-v2: false",
        "ds-cfg-keep-stats: false",
        "ds-cfg-use-tcp-keep-alive: true",
        "ds-cfg-use-tcp-no-delay: true",
        "ds-cfg-allow-tcp-reuse-address: true",
        "ds-cfg-send-rejection-notice: true",
        "ds-cfg-max-request-size: 5 megabytes",
        "ds-cfg-num-request-handlers: 2",
        "ds-cfg-allow-start-tls: false",
        "ds-cfg-use-ssl: false",
        "ds-cfg-ssl-client-auth-policy: optional",
        "ds-cfg-ssl-cert-nickname: server-cert",
        "ds-cfg-key-manager-provider: cn=JKS,cn=Key Manager Providers,cn=config",
        "ds-cfg-trust-manager-provider: cn=JKS,cn=Trust Manager Providers,cn=config");
    LDAPConnectionHandler2 handler = getLDAPHandlerInstance(handlerEntry);
    int listenPort = handler.getListeners().iterator().next().getPort();
    try
    {
      handler.start();
      // No retry loop here on purpose: once start() has returned, the port must already be open.
      TestCaseUtils.assertPortIsAcceptingConnections(listenPort);
    }
    finally
    {
      handler.processServerShutdown(reasonMsg);
      handler.finalizeConnectionHandler(reasonMsg);
      handler.join(10000);
      assertFalse(handler.isAlive(), "the connection handler thread is still running");
    }
  }
  /**
   *  Start a handler an then give its hasAcceptableConfiguration a Entry with
   *  numerous invalid cases and single-valued attrs with duplicate values.
   *
opendj-server-legacy/src/test/java/org/opends/server/replication/server/ReplicationServerDynamicConfTest.java
@@ -13,15 +13,31 @@
 *
 * Copyright 2006-2009 Sun Microsystems, Inc.
 * Portions Copyright 2013-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.opends.server.replication.server;
import static org.opends.server.TestCaseUtils.*;
import static org.opends.server.util.StaticUtils.*;
import static org.testng.Assert.*;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ResultCode;
import org.opends.server.TestCaseUtils;
import org.opends.server.backends.ChangelogBackend;
import org.opends.server.core.DirectoryServer;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.service.ReplicationBroker;
import org.opends.server.types.VirtualAttributeRule;
import org.forgerock.opendj.ldap.DN;
import org.testng.annotations.Test;
@@ -47,6 +63,7 @@
      // instantiate a Replication server using the first port number.
      ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration(ports[0], null, 0, 1, 0, 0, null);
      replicationServer = new ReplicationServer(conf);
      assertTrue(replicationServer.isListening(), "the replication server should listen on port " + ports[0]);
      // Most of the configuration change are trivial to apply.
      // The interesting change is the change of the replication server port.
@@ -62,10 +79,199 @@
      // check that the sendWindow is not null to make sure that the
      // broker did connect successfully.
      assertTrue(broker.getCurrentSendWindow() != 0);
      assertTrue(replicationServer.isListening(), "the replication server should listen on port " + ports[1]);
    }
    finally
    {
      remove(replicationServer);
    }
  }
  /**
   * Tests that a replication server whose listen port cannot be bound fails fast instead
   * of silently starting without any listener, which used to surface much later, and in
   * an unrelated place, as a "connection refused", and that aborting its initialization
   * leaves the external changelog of the replication server which is already running
   * untouched: the virtual attribute rules are registered globally, by attribute name.
   */
  @Test
  public void replServerFailsWhenListenPortIsInUse() throws Exception
  {
    TestCaseUtils.startServer();
    ReplicationServer runningServer = null;
    try
    {
      final int[] ports = TestCaseUtils.findFreePorts(1);
      runningServer = new ReplicationServer(new ReplServerFakeConfiguration(
          ports[0], "replServerFailsWhenListenPortIsInUseRunningDb", 0, 1, 0, 0, null));
      assertTrue(runningServer.isListening());
      final List<String> rulesBefore = changelogVirtualAttributeNames();
      assertFalse(rulesBefore.isEmpty(), "the running replication server should provide the external changelog");
      final int instancesBefore = ReplicationServer.getAllInstances().size();
      // Keep the port bound for the whole lifetime of the replication server creation.
      final String dbDirName = "replServerFailsWhenListenPortIsInUseDb";
      try (ServerSocket portHolder = TestCaseUtils.bindFreePort())
      {
        final ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration(
            portHolder.getLocalPort(), dbDirName, 0, 1, 0, 0, null);
        try
        {
          final ReplicationServer replicationServer = new ReplicationServer(conf);
          remove(replicationServer);
          fail("Creating a replication server on a port already in use should have failed");
        }
        catch (ConfigException expected)
        {
          // The failed replication server must not be left registered anywhere,
          assertEquals(ReplicationServer.getAllInstances().size(), instancesBefore);
          // nor must it release what it never acquired.
          assertEquals(changelogVirtualAttributeNames(), rulesBefore,
              "aborting the initialization must not deregister the virtual attribute rules"
                  + " of the running replication server");
          assertTrue(DirectoryServer.getInstance().getServerContext().getBackendConfigManager()
              .hasLocalBackend(ChangelogBackend.BACKEND_ID), "the changelog backend should still be registered");
          assertTrue(runningServer.isListening(), "the running replication server should still listen");
        }
        finally
        {
          // The aborted instance is never handed to the test, so its changelog cannot be
          // removed through ReplicationTestCase.remove().
          recursiveDelete(getFileForPath(dbDirName));
        }
      }
    }
    finally
    {
      remove(runningServer);
    }
  }
  /**
   * Tests that a listen port which is only momentarily unavailable, as it happens when a
   * socket holding it is being closed, does not prevent the replication server from
   * starting: {@code bindListenPort()} retries the bind a few times.
   * <p>
   * The port is released as soon as the replication server has actually failed to bind it,
   * so the retry is the only thing which can make it start: a test releasing the port after
   * a delay would silently stop exercising the retry as soon as the replication server took
   * longer than that delay to reach its first attempt.
   */
  @Test
  public void replServerRetriesToBindItsListenPort() throws Exception
  {
    TestCaseUtils.startServer();
    ReplicationServer replicationServer = null;
    final ServerSocket portHolder = TestCaseUtils.bindFreePort();
    final int bindFailuresBefore = ReplicationServer.listenPortBindFailures.get();
    try
    {
      final Thread portReleaser = new Thread(() -> {
        try
        {
          final long deadline = System.currentTimeMillis() + 30000;
          while (ReplicationServer.listenPortBindFailures.get() == bindFailuresBefore
              && System.currentTimeMillis() < deadline)
          {
            Thread.sleep(10);
          }
        }
        catch (InterruptedException e)
        {
          Thread.currentThread().interrupt();
        }
        close(portHolder);
      }, "port releaser of replServerRetriesToBindItsListenPort");
      portReleaser.start();
      replicationServer = new ReplicationServer(new ReplServerFakeConfiguration(
          portHolder.getLocalPort(), "replServerRetriesToBindItsListenPortDb", 0, 1, 0, 0, null));
      portReleaser.join();
      assertTrue(ReplicationServer.listenPortBindFailures.get() > bindFailuresBefore,
          "the replication server should have failed its first attempt to bind the port,"
              + " otherwise this test does not exercise the retry");
      assertTrue(replicationServer.isListening(),
          "the replication server should have bound the port which was released while it was retrying");
    }
    finally
    {
      close(portHolder);
      remove(replicationServer);
    }
  }
  /**
   * Tests that a port change to a port which is not available is rejected, and that a
   * replication server which nevertheless goes through the change keeps its listen port
   * and its whole configuration: the new port is bound before the current one is released,
   * so a failure has nothing to roll back and leaves nothing half applied.
   */
  @Test
  public void replServerKeepsItsConfigurationWhenAPortChangeFails() throws Exception
  {
    TestCaseUtils.startServer();
    ReplicationServer replicationServer = null;
    try
    {
      final int[] ports = TestCaseUtils.findFreePorts(1);
      final String dbDirName = "replServerKeepsItsConfigurationWhenAPortChangeFailsDb";
      replicationServer = new ReplicationServer(new ReplServerFakeConfiguration(
          ports[0], dbDirName, 0, 1, 0, 0, null, 1, 2000, 5000, 1));
      assertTrue(replicationServer.isListening());
      try (ServerSocket portHolder = TestCaseUtils.bindFreePort())
      {
        // The weight changes too, so that a failed change can be seen not to have applied
        // the part of the new configuration which does not depend on the listen port.
        final ReplServerFakeConfiguration newConf = new ReplServerFakeConfiguration(
            portHolder.getLocalPort(), dbDirName, 0, 1, 0, 0, null, 1, 2000, 5000, 2);
        final List<LocalizableMessage> unacceptableReasons = new ArrayList<>();
        assertFalse(replicationServer.isConfigurationChangeAcceptable(newConf, unacceptableReasons),
            "a change to a listen port which is in use should not be acceptable");
        assertFalse(unacceptableReasons.isEmpty(), "the rejected change should say why it was rejected");
        final ConfigChangeResult ccr = replicationServer.applyConfigurationChange(newConf);
        assertEquals(ccr.getResultCode(), ResultCode.OPERATIONS_ERROR);
        assertFalse(ccr.getMessages().isEmpty(), "the failed change should say why it failed");
        assertEquals(replicationServer.getReplicationPort(), ports[0],
            "the replication server should have kept its previous listen port");
        assertTrue(replicationServer.isListening(), "the replication server should still listen");
        assertEquals(replicationServer.getWeight(), 1,
            "a failed port change must not apply the rest of the new configuration");
      }
      // and it must still be usable on its original port.
      ReplicationBroker broker = openReplicationSession(
          DN.valueOf(TEST_ROOT_DN_STRING), 1, 10, ports[0], 1000);
      assertTrue(broker.getCurrentSendWindow() != 0);
    }
    finally
    {
      remove(replicationServer);
    }
  }
  /** Returns the names of the virtual attributes provided by the external changelog. */
  private List<String> changelogVirtualAttributeNames()
  {
    final Collection<String> changelogAttributes = Arrays.asList(
        "lastexternalchangelogcookie", "firstchangenumber", "lastchangenumber", "changelog");
    final List<String> names = new ArrayList<>();
    for (VirtualAttributeRule rule : DirectoryServer.getVirtualAttributes())
    {
      final String name = rule.getAttributeType().getNameOrOID().toLowerCase();
      if (changelogAttributes.contains(name))
      {
        names.add(name);
      }
    }
    Collections.sort(names);
    return names;
  }
}
opendj-server/src/main/java/org/forgerock/opendj/server/core/ProductInformation.java
@@ -12,6 +12,7 @@
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2014-2016 ForgeRock AS.
 * Portions Copyright 2026 3A Systems, LLC.
 */
package org.forgerock.opendj.server.core;
@@ -43,7 +44,7 @@
    private ProductInformation(final String productName) {
        final String resourceName = "/META-INF/product/" + productName + ".properties";
        final InputStream stream = getClass().getResourceAsStream(resourceName);
        final InputStream stream = ProductInformation.class.getResourceAsStream(resourceName);
        if (stream == null) {
            throw new MissingResourceException("Can't find product information " + resourceName,