From 3608e9e2209a467d9384dd152844c1b55f83373c Mon Sep 17 00:00:00 2001
From: maximthomas <maxim.thomas@gmail.com>
Date: Fri, 18 Jul 2025 08:34:36 +0000
Subject: [PATCH] Merge branch 'master' into update-jdk-11
---
opendj-packages/opendj-docker/pom.xml | 10
opendj-embedded/pom.xml | 6
opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java | 70
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java | 5
opendj-packages/opendj-svr4/pom.xml | 2
opendj-server-legacy/resource/schema/02-config.ldif | 11
opendj-rest2ldap/pom.xml | 2
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java | 25
opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml | 2
opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java | 14
opendj-ldap-sdk-examples/pom.xml | 2
opendj-server-legacy/pom.xml | 4
opendj-ldap-toolkit/pom.xml | 2
opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml | 55 +
opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java | 96 ++
opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java | 182 +++++
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java | 8
opendj-cli/pom.xml | 2
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java | 24
opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template | 2
opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java | 66 +
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java | 24
opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java | 82 ++
opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java | 24
opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java | 10
opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java | 29
opendj-doc-maven-plugin/pom.xml | 14
opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java | 26
opendj-dsml-servlet/pom.xml | 11
opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template | 4
opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml | 75 ++
opendj-embedded-server-examples/pom.xml | 2
opendj-rest2ldap-servlet/pom.xml | 2
opendj-legacy/pom.xml | 2
opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java | 8
opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java | 52 +
opendj-packages/opendj-msi/opendj-msi-standard/pom.xml | 2
opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java | 101 ++
opendj-doc-generated-ref/pom.xml | 6
opendj-packages/opendj-msi/pom.xml | 2
opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java | 153 ++++
pom.xml | 142 +--
opendj-server-legacy/resource/config/config.ldif | 17
opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc | 9
opendj-grizzly/pom.xml | 11
opendj-maven-plugin/pom.xml | 12
opendj-server-example-plugin/pom.xml | 7
opendj-config/pom.xml | 2
opendj-packages/pom.xml | 2
opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java | 260 +++++++
opendj-server-msad-plugin/pom.xml | 2
opendj-openidm-account-change-notification-handler/pom.xml | 2
opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java | 168 ++++
opendj-server/pom.xml | 6
opendj-packages/opendj-deb/opendj-deb-standard/pom.xml | 2
opendj-packages/opendj-deb/pom.xml | 2
opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java | 94 ++
opendj-core/pom.xml | 20
opendj-packages/opendj-rpm/pom.xml | 2
opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java | 131 +++
60 files changed, 1,866 insertions(+), 244 deletions(-)
diff --git a/opendj-cli/pom.xml b/opendj-cli/pom.xml
index f22b1df..1a99887 100644
--- a/opendj-cli/pom.xml
+++ b/opendj-cli/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-cli</artifactId>
diff --git a/opendj-config/pom.xml b/opendj-config/pom.xml
index 7c19aec..387b639 100644
--- a/opendj-config/pom.xml
+++ b/opendj-config/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-config</artifactId>
<name>OpenDJ Configuration API</name>
diff --git a/opendj-core/pom.xml b/opendj-core/pom.xml
index bff3bcf..1a80a9d 100644
--- a/opendj-core/pom.xml
+++ b/opendj-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-core</artifactId>
@@ -57,9 +57,9 @@
</dependency>
<dependency>
- <groupId>io.reactivex.rxjava2</groupId>
+ <groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
- <version>2.2.21</version>
+ <version>3.1.10</version>
</dependency>
<dependency>
@@ -99,13 +99,13 @@
<artifactId>bc-fips</artifactId>
<version>${bc.fips.version}</version>
</dependency>
-
+
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bctls-fips</artifactId>
<version>${bctls.fips.version}</version>
</dependency>
-
+
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@@ -114,8 +114,8 @@
<properties>
- <bc.fips.version>1.0.2.5</bc.fips.version>
- <bctls.fips.version>1.0.19</bctls.fips.version>
+ <bc.fips.version>2.1.0</bc.fips.version>
+ <bctls.fips.version>2.1.20</bctls.fips.version>
<opendj.osgi.import.additional>
com.sun.security.auth*;resolution:=optional
@@ -182,6 +182,7 @@
org.forgerock.opendj.ldif,
com.forgerock.reactive
</Export-Package>
+ <Import-Package>!org.bouncycastle.jcajce.provider</Import-Package>
<Build-Maven>Apache Maven ${maven.version}</Build-Maven>
<SCM-Revision>${buildRevision}</SCM-Revision>
<SCM-Branch>${scmBranch}</SCM-Branch>
@@ -198,11 +199,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- </plugin>
-
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java
new file mode 100644
index 0000000..35c4981
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/AbortedTransactionExtendedResult.java
@@ -0,0 +1,94 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResult;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+
+/*
+ The Aborted Transaction Notice is an Unsolicited Notification message
+ where the responseName is 1.3.6.1.1.21.4 and responseValue is present
+ and contains a transaction identifier.
+ */
+public class AbortedTransactionExtendedResult extends AbstractExtendedResult<AbortedTransactionExtendedResult> {
+ @Override
+ public String getOID() {
+ return "1.3.6.1.1.21.4";
+ }
+
+ private AbortedTransactionExtendedResult(final ResultCode resultCode) {
+ super(resultCode);
+ }
+
+ public static AbortedTransactionExtendedResult newResult(final ResultCode resultCode) {
+ Reject.ifNull(resultCode);
+ return new AbortedTransactionExtendedResult(resultCode);
+ }
+
+ private String transactionID = null;
+
+ public AbortedTransactionExtendedResult setTransactionID(final String transactionID) {
+ this.transactionID = transactionID;
+ return this;
+ }
+
+ public String getTransactionID() {
+ return transactionID;
+ }
+
+ @Override
+ public ByteString getValue() {
+ final ByteStringBuilder buffer = new ByteStringBuilder();
+ final ASN1Writer writer = ASN1.getWriter(buffer);
+ try {
+ writer.writeOctetString(transactionID);
+ } catch (final IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ return buffer.toByteString();
+ }
+
+ @Override
+ public boolean hasValue() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "AbortedTransactionExtendedResult(resultCode=" +
+ getResultCode() +
+ ", matchedDN=" +
+ getMatchedDN() +
+ ", diagnosticMessage=" +
+ getDiagnosticMessage() +
+ ", referrals=" +
+ getReferralURIs() +
+ ", responseName=" +
+ getOID() +
+ ", transactionID=" +
+ transactionID +
+ ", controls=" +
+ getControls() +
+ ")";
+ }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java
new file mode 100644
index 0000000..5b898a1
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedRequest.java
@@ -0,0 +1,182 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Reader;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.requests.*;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResultDecoder;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResultDecoder;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
+import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
+
+/*
+ An End Transaction Request is an LDAPMessage of CHOICE extendedReq
+ where the requestName is 1.3.6.1.1.21.3 and the requestValue is
+ present and contains a BER-encoded txnEndReq.
+
+ txnEndReq ::= SEQUENCE {
+ commit BOOLEAN DEFAULT TRUE,
+ identifier OCTET STRING }
+
+ A commit value of TRUE indicates a request to commit the transaction
+ identified by the identifier. A commit value of FALSE indicates a
+ request to abort the identified transaction.
+ */
+public class EndTransactionExtendedRequest extends AbstractExtendedRequest<EndTransactionExtendedRequest, EndTransactionExtendedResult> {
+
+ public static final String END_TRANSACTION_REQUEST_OID ="1.3.6.1.1.21.3";
+
+ @Override
+ public String getOID() {
+ return END_TRANSACTION_REQUEST_OID;
+ }
+
+ @Override
+ public ExtendedResultDecoder<EndTransactionExtendedResult> getResultDecoder() {
+ return RESULT_DECODER;
+ }
+
+ Boolean commit=true;
+ public EndTransactionExtendedRequest setCommit(final Boolean commit) {
+ this.commit = commit;
+ return this;
+ }
+
+ String transactionID;
+ public EndTransactionExtendedRequest setTransactionID(final String transactionID) {
+ Reject.ifNull(transactionID);
+ this.transactionID = transactionID;
+ return this;
+ }
+ public boolean isCommit() {
+ return commit;
+ }
+
+ public String getTransactionID() {
+ return transactionID;
+ }
+
+ @Override
+ public ByteString getValue() {
+ Reject.ifNull(transactionID);
+ final ByteStringBuilder buffer = new ByteStringBuilder();
+ final ASN1Writer writer = ASN1.getWriter(buffer);
+ try {
+ writer.writeStartSequence();
+ if (commit!=null) {
+ writer.writeBoolean(commit);
+ }
+ writer.writeOctetString(transactionID);
+ writer.writeEndSequence();
+ } catch (final IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ return buffer.toByteString();
+ }
+
+ @Override
+ public boolean hasValue() {
+ return true;
+ }
+
+ private static final EndTransactionExtendedRequest.ResultDecoder RESULT_DECODER = new EndTransactionExtendedRequest.ResultDecoder();
+
+ private static final class ResultDecoder extends AbstractExtendedResultDecoder<EndTransactionExtendedResult> {
+ @Override
+ public EndTransactionExtendedResult newExtendedErrorResult(final ResultCode resultCode,final String matchedDN, final String diagnosticMessage) {
+ if (!resultCode.isExceptional()) {
+ throw new IllegalArgumentException("No response name and value for result code "+ resultCode.intValue());
+ }
+ return EndTransactionExtendedResult.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(diagnosticMessage);
+ }
+
+ /*
+ txnEndRes ::= SEQUENCE {
+ messageID MessageID OPTIONAL,
+ -- msgid associated with non-success resultCode
+ updatesControls SEQUENCE OF updateControls SEQUENCE {
+ messageID MessageID,
+ -- msgid associated with controls
+ controls Controls
+ } OPTIONAL
+ }
+ */
+ @Override
+ public EndTransactionExtendedResult decodeExtendedResult(final ExtendedResult result,final DecodeOptions options) throws DecodeException {
+ if (result instanceof EndTransactionExtendedResult) {
+ return (EndTransactionExtendedResult) result;
+ }
+
+ final ResultCode resultCode = result.getResultCode();
+ final EndTransactionExtendedResult newResult =
+ EndTransactionExtendedResult.newResult(resultCode)
+ .setMatchedDN(result.getMatchedDN())
+ .setDiagnosticMessage(result.getDiagnosticMessage());
+
+ final ByteString responseValue = result.getValue();
+ if (!resultCode.isExceptional() && responseValue == null) {
+ throw DecodeException.error(LocalizableMessage.raw("Empty response value"));
+ }
+ if (responseValue != null) {
+ try {
+ final ASN1Reader reader = ASN1.getReader(responseValue);
+ if (reader.hasNextElement()) {
+ reader.readStartSequence();
+ if (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_INTEGER_TYPE) {
+ newResult.setFailedMessageID(Math.toIntExact(reader.readInteger()));
+ } else if (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_SEQUENCE_TYPE) {
+ reader.readStartSequence();
+ while (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_SEQUENCE_TYPE) {
+ reader.readStartSequence();
+ final long messageId = reader.readInteger();
+ final List<Control> controls = new ArrayList<>();
+ reader.readStartSequence();
+ while (reader.hasNextElement() && reader.peekType() == ASN1.UNIVERSAL_OCTET_STRING_TYPE) {
+ final ByteString controlEncoded = reader.readOctetString();
+ //TODO decode Control
+ }
+ reader.readEndSequence();
+ //newResult.success(messageId, controls.toArray(new Control[]{}));
+ reader.readEndSequence();
+ }
+ reader.readEndSequence();
+ }
+ reader.readEndSequence();
+ }
+ } catch (final IOException e) {
+ throw DecodeException.error(LocalizableMessage.raw("Error decoding response value"), e);
+ }
+ }
+ for (final Control control : result.getControls()) {
+ newResult.addControl(control);
+ }
+ return newResult;
+ }
+ }
+}
+
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java
new file mode 100644
index 0000000..a4ba23e
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/EndTransactionExtendedResult.java
@@ -0,0 +1,168 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResult;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/*
+ An End Transaction Response is an LDAPMessage sent in response to a
+ End Transaction Request. Its response name is absent. The
+ responseValue when present contains a BER-encoded txnEndRes.
+
+ txnEndRes ::= SEQUENCE {
+ messageID MessageID OPTIONAL,
+ -- msgid associated with non-success resultCode
+ updatesControls SEQUENCE OF updateControls SEQUENCE {
+ messageID MessageID,
+ -- msgid associated with controls
+ controls Controls
+ } OPTIONAL
+ }
+ -- where MessageID and Controls are as specified in RFC 4511
+
+ The txnEndRes.messageID provides the message id of the update request
+ associated with a non-success response. txnEndRes.messageID is
+ absent when resultCode of the End Transaction Response is success
+
+ The txnEndRes.updatesControls provides a facility for returning
+ response controls that normally (i.e., in the absence of
+ transactions) would be returned in an update response. The
+ updateControls.messageID provides the message id of the update
+ request associated with the response controls provided in
+ updateControls.controls.
+
+ The txnEndRes.updatesControls is absent when there are no update
+ response controls to return.
+
+ If both txnEndRes.messageID and txnEndRes.updatesControl are absent,
+ the responseValue of the End Transaction Response is absent.
+ */
+public class EndTransactionExtendedResult extends AbstractExtendedResult<EndTransactionExtendedResult> {
+ @Override
+ public String getOID() {
+ return EndTransactionExtendedRequest.END_TRANSACTION_REQUEST_OID;
+ }
+
+ private EndTransactionExtendedResult(final ResultCode resultCode) {
+ super(resultCode);
+ }
+
+ public static EndTransactionExtendedResult newResult(final ResultCode resultCode) {
+ Reject.ifNull(resultCode);
+ return new EndTransactionExtendedResult(resultCode);
+ }
+
+ // The message ID for the operation that failed, if applicable.
+ Integer failedOpMessageID=null;
+
+ public EndTransactionExtendedResult setFailedMessageID(final Integer failedOpMessageID) {
+ Reject.ifNull(failedOpMessageID);
+ this.failedOpMessageID = failedOpMessageID;
+ return this;
+ }
+
+ // A mapping of the response controls for the operations performed as part of
+ // the transaction.
+ Map<Integer, List<Control>> opResponseControls= new TreeMap<>();
+
+ public EndTransactionExtendedResult success(Integer messageID, List<Control> responses) {
+ Reject.ifNull(messageID);
+ Reject.ifNull(responses);
+ opResponseControls.put(messageID,responses);
+ return this;
+ }
+
+ /*
+ txnEndRes ::= SEQUENCE {
+ messageID MessageID OPTIONAL,
+ -- msgid associated with non-success resultCode
+ updatesControls SEQUENCE OF updateControls SEQUENCE {
+ messageID MessageID,
+ -- msgid associated with controls
+ controls Controls
+ } OPTIONAL
+ }
+ */
+ @Override
+ public ByteString getValue() {
+ final ByteStringBuilder buffer = new ByteStringBuilder();
+ final ASN1Writer writer = ASN1.getWriter(buffer);
+ try {
+ if (failedOpMessageID!=null || (opResponseControls!=null && !opResponseControls.isEmpty()) ) {
+ writer.writeStartSequence();
+ if (failedOpMessageID != null) {
+ writer.writeInteger(failedOpMessageID);
+ }
+ if (opResponseControls != null && !opResponseControls.isEmpty()) {
+ writer.writeStartSequence();
+ for (Map.Entry<Integer, List<Control>> entry : opResponseControls.entrySet()) {
+ writer.writeStartSequence();
+ writer.writeInteger(entry.getKey());
+ writer.writeStartSequence();
+ for (Control control : entry.getValue()) {
+ writer.writeOctetString(control.getValue());
+ }
+ writer.writeEndSequence();
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ }
+ writer.writeEndSequence();
+ }
+ } catch (final IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ return buffer.toByteString();
+ }
+
+ @Override
+ public boolean hasValue() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "EndTransactionExtendedResult(resultCode=" +
+ getResultCode() +
+ ", matchedDN=" +
+ getMatchedDN() +
+ ", diagnosticMessage=" +
+ getDiagnosticMessage() +
+ ", referrals=" +
+ getReferralURIs() +
+ ", responseName=" +
+ getOID() +
+ ", failedOpMessageID=" +
+ failedOpMessageID +
+ ", opResponseControls=" +
+ opResponseControls +
+ ", controls=" +
+ getControls() +
+ ")";
+ }
+}
\ No newline at end of file
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java
new file mode 100644
index 0000000..647063d
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedRequest.java
@@ -0,0 +1,101 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Reader;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.requests.AbstractExtendedRequest;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResultDecoder;
+import org.forgerock.opendj.ldap.responses.ExtendedResult;
+import org.forgerock.opendj.ldap.responses.ExtendedResultDecoder;
+
+import java.io.IOException;
+
+/*
+ A Start Transaction Request is an LDAPMessage of CHOICE extendedReq
+ where the requestName is 1.3.6.1.1.21.1 and the requestValue is
+ absent.
+ */
+public class StartTransactionExtendedRequest extends AbstractExtendedRequest<StartTransactionExtendedRequest, StartTransactionExtendedResult> {
+ public static final String START_TRANSACTION_REQUEST_OID ="1.3.6.1.1.21.1";
+
+ @Override
+ public String getOID() {
+ return START_TRANSACTION_REQUEST_OID;
+ }
+ @Override
+ public ExtendedResultDecoder<StartTransactionExtendedResult> getResultDecoder() {
+ return RESULT_DECODER;
+ }
+
+ @Override
+ public ByteString getValue() {
+ return null;
+ }
+
+ @Override
+ public boolean hasValue() {
+ return false;
+ }
+
+ private static final StartTransactionExtendedRequest.ResultDecoder RESULT_DECODER = new StartTransactionExtendedRequest.ResultDecoder();
+
+ private static final class ResultDecoder extends AbstractExtendedResultDecoder<StartTransactionExtendedResult> {
+ @Override
+ public StartTransactionExtendedResult newExtendedErrorResult(final ResultCode resultCode,final String matchedDN, final String diagnosticMessage) {
+ if (!resultCode.isExceptional()) {
+ throw new IllegalArgumentException("No response name and value for result code "+ resultCode.intValue());
+ }
+ return StartTransactionExtendedResult.newResult(resultCode).setMatchedDN(matchedDN).setDiagnosticMessage(diagnosticMessage);
+ }
+
+ @Override
+ public StartTransactionExtendedResult decodeExtendedResult(final ExtendedResult result,final DecodeOptions options) throws DecodeException {
+ if (result instanceof StartTransactionExtendedResult) {
+ return (StartTransactionExtendedResult) result;
+ }
+
+ final ResultCode resultCode = result.getResultCode();
+ final StartTransactionExtendedResult newResult =
+ StartTransactionExtendedResult.newResult(resultCode)
+ .setMatchedDN(result.getMatchedDN())
+ .setDiagnosticMessage(result.getDiagnosticMessage());
+
+ final ByteString responseValue = result.getValue();
+ if (!resultCode.isExceptional() && responseValue == null) {
+ throw DecodeException.error(LocalizableMessage.raw("Empty response value"));
+ }
+ if (responseValue != null) {
+ try {
+ final ASN1Reader reader = ASN1.getReader(responseValue);
+ newResult.setTransactionID(reader.readOctetStringAsString());
+ } catch (final IOException e) {
+ throw DecodeException.error(LocalizableMessage.raw("Error decoding response value"), e);
+ }
+ }
+ for (final Control control : result.getControls()) {
+ newResult.addControl(control);
+ }
+ return newResult;
+ }
+ }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java
new file mode 100644
index 0000000..40dbf04
--- /dev/null
+++ b/opendj-core/src/main/java/com/forgerock/opendj/ldap/extensions/StartTransactionExtendedResult.java
@@ -0,0 +1,96 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package com.forgerock.opendj.ldap.extensions;
+
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Writer;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ByteStringBuilder;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.responses.AbstractExtendedResult;
+import org.forgerock.util.Reject;
+
+import java.io.IOException;
+
+/*
+ A Start Transaction Response is an LDAPMessage of CHOICE extendedRes
+ sent in response to a Start Transaction Request. Its responseName is
+ absent. When the resultCode is success (0), responseValue is present
+ and contains a transaction identifier. Otherwise, the responseValue
+ is absent.
+ */
+public class StartTransactionExtendedResult extends AbstractExtendedResult<StartTransactionExtendedResult> {
+ @Override
+ public String getOID() {
+ return StartTransactionExtendedRequest.START_TRANSACTION_REQUEST_OID;
+ }
+
+ private StartTransactionExtendedResult(final ResultCode resultCode) {
+ super(resultCode);
+ }
+
+ public static StartTransactionExtendedResult newResult(final ResultCode resultCode) {
+ Reject.ifNull(resultCode);
+ return new StartTransactionExtendedResult(resultCode);
+ }
+
+ private String transactionID = null;
+
+ public StartTransactionExtendedResult setTransactionID(final String transactionID) {
+ this.transactionID = transactionID;
+ return this;
+ }
+
+ public String getTransactionID() {
+ return transactionID;
+ }
+
+ @Override
+ public ByteString getValue() {
+ final ByteStringBuilder buffer = new ByteStringBuilder();
+ final ASN1Writer writer = ASN1.getWriter(buffer);
+ try {
+ writer.writeOctetString(transactionID);
+ } catch (final IOException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ return buffer.toByteString();
+ }
+
+ @Override
+ public boolean hasValue() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "StartTransactionExtendedResult(resultCode=" +
+ getResultCode() +
+ ", matchedDN=" +
+ getMatchedDN() +
+ ", diagnosticMessage=" +
+ getDiagnosticMessage() +
+ ", referrals=" +
+ getReferralURIs() +
+ ", responseName=" +
+ getOID() +
+ ", transactionID=" +
+ transactionID +
+ ", controls=" +
+ getControls() +
+ ")";
+ }
+}
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java b/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
index 2ef0205..1bb350f 100644
--- a/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
+++ b/opendj-core/src/main/java/com/forgerock/reactive/RxJavaStreams.java
@@ -18,13 +18,13 @@
import org.forgerock.util.Function;
import org.reactivestreams.Publisher;
-import io.reactivex.CompletableEmitter;
-import io.reactivex.CompletableOnSubscribe;
-import io.reactivex.CompletableSource;
-import io.reactivex.Flowable;
-import io.reactivex.SingleEmitter;
-import io.reactivex.SingleOnSubscribe;
-import io.reactivex.SingleSource;
+import io.reactivex.rxjava3.core.CompletableEmitter;
+import io.reactivex.rxjava3.core.CompletableOnSubscribe;
+import io.reactivex.rxjava3.core.CompletableSource;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.SingleEmitter;
+import io.reactivex.rxjava3.core.SingleOnSubscribe;
+import io.reactivex.rxjava3.core.SingleSource;
/**
* {@link Stream} and {@link Single} implementations based on RxJava.
@@ -96,7 +96,7 @@
* @return A new {@link Stream}
*/
public static <V> Single<V> singleFromPublisher(final Publisher<V> publisher) {
- return new RxJavaSingle<>(io.reactivex.Single.fromPublisher(publisher));
+ return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.fromPublisher(publisher));
}
/**
@@ -109,7 +109,7 @@
* @return A new {@link Single}
*/
public static <V> Single<V> singleFrom(final V value) {
- return new RxJavaSingle<>(io.reactivex.Single.just(value));
+ return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.just(value));
}
/**
@@ -122,7 +122,7 @@
* @return A new {@link Single}
*/
public static <V> Single<V> singleError(final Throwable error) {
- return new RxJavaSingle<>(io.reactivex.Single.<V>error(error));
+ return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.<V>error(error));
}
/**
@@ -135,7 +135,7 @@
* @return A new {@link Single}
*/
public static <V> Single<V> newSingle(final Single.Emitter<V> emitter) {
- return new RxJavaSingle<>(io.reactivex.Single.create(new SingleOnSubscribe<V>() {
+ return new RxJavaSingle<>(io.reactivex.rxjava3.core.Single.create(new SingleOnSubscribe<V>() {
@Override
public void subscribe(final SingleEmitter<V> e) throws Exception {
emitter.subscribe(new Single.Subscriber<V>() {
@@ -161,7 +161,7 @@
* @return A new {@link Completable}
*/
public static Completable newCompletable(final Completable.Emitter onSubscribe) {
- return new RxJavaCompletable(io.reactivex.Completable.create(new CompletableOnSubscribe() {
+ return new RxJavaCompletable(io.reactivex.rxjava3.core.Completable.create(new CompletableOnSubscribe() {
@Override
public void subscribe(final CompletableEmitter e) throws Exception {
onSubscribe.subscribe(new Completable.Subscriber() {
@@ -186,7 +186,7 @@
* @return A new {@link Completable}
*/
public static Completable completableError(final Throwable error) {
- return new RxJavaCompletable(io.reactivex.Completable.error(error));
+ return new RxJavaCompletable(io.reactivex.rxjava3.core.Completable.error(error));
}
private static final class RxJavaStream<V> implements Stream<V> {
@@ -204,7 +204,7 @@
@Override
public <O> Stream<O> map(final Function<V, O, Exception> function) {
- return new RxJavaStream<>(impl.map(new io.reactivex.functions.Function<V, O>() {
+ return new RxJavaStream<>(impl.map(new io.reactivex.rxjava3.functions.Function<V, O>() {
@Override
public O apply(V t) throws Exception {
return function.apply(t);
@@ -215,7 +215,7 @@
@Override
public <O> Stream<O> flatMap(final Function<? super V, ? extends Publisher<? extends O>, Exception> function,
int maxConcurrency) {
- return new RxJavaStream<>(impl.flatMap(new io.reactivex.functions.Function<V, Publisher<? extends O>>() {
+ return new RxJavaStream<>(impl.flatMap(new io.reactivex.rxjava3.functions.Function<V, Publisher<? extends O>>() {
@Override
public Publisher<? extends O> apply(V t) throws Exception {
return function.apply(t);
@@ -225,7 +225,7 @@
@Override
public Stream<V> onNext(final Consumer<V> onNext) {
- return new RxJavaStream<>(impl.doOnNext(new io.reactivex.functions.Consumer<V>() {
+ return new RxJavaStream<>(impl.doOnNext(new io.reactivex.rxjava3.functions.Consumer<V>() {
@Override
public void accept(V value) throws Exception {
onNext.accept(value);
@@ -235,7 +235,7 @@
@Override
public Stream<V> onError(final Consumer<Throwable> onError) {
- return new RxJavaStream<>(impl.doOnError(new io.reactivex.functions.Consumer<Throwable>() {
+ return new RxJavaStream<>(impl.doOnError(new io.reactivex.rxjava3.functions.Consumer<Throwable>() {
@Override
public void accept(Throwable t) throws Exception {
onError.accept(t);
@@ -246,7 +246,7 @@
@Override
public Stream<V> onErrorResumeWith(final Function<Throwable, Publisher<V>, Exception> function) {
return new RxJavaStream<>(
- impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, Publisher<? extends V>>() {
+ impl.onErrorResumeNext(new io.reactivex.rxjava3.functions.Function<Throwable, Publisher<? extends V>>() {
@Override
public Publisher<? extends V> apply(Throwable t) throws Exception {
return function.apply(t);
@@ -256,7 +256,7 @@
@Override
public Stream<V> onComplete(final Action action) {
- return new RxJavaStream<>(impl.doOnComplete(new io.reactivex.functions.Action() {
+ return new RxJavaStream<>(impl.doOnComplete(new io.reactivex.rxjava3.functions.Action() {
@Override
public void run() throws Exception {
action.run();
@@ -272,9 +272,9 @@
private static final class RxJavaSingle<V> implements Single<V> {
- private final io.reactivex.Single<V> impl;
+ private final io.reactivex.rxjava3.core.Single<V> impl;
- private RxJavaSingle(io.reactivex.Single<V> impl) {
+ private RxJavaSingle(io.reactivex.rxjava3.core.Single<V> impl) {
this.impl = impl;
}
@@ -285,7 +285,7 @@
@Override
public <O> Single<O> map(final Function<V, O, Exception> function) {
- return new RxJavaSingle<>(impl.map(new io.reactivex.functions.Function<V, O>() {
+ return new RxJavaSingle<>(impl.map(new io.reactivex.rxjava3.functions.Function<V, O>() {
@Override
public O apply(V t) throws Exception {
return function.apply(t);
@@ -300,12 +300,12 @@
@Override
public void subscribe(final Consumer<? super V> resultConsumer, final Consumer<Throwable> errorConsumer) {
- impl.subscribe(new io.reactivex.functions.Consumer<V>() {
+ impl.subscribe(new io.reactivex.rxjava3.functions.Consumer<V>() {
@Override
public void accept(V t) throws Exception {
resultConsumer.accept(t);
}
- }, new io.reactivex.functions.Consumer<Throwable>() {
+ }, new io.reactivex.rxjava3.functions.Consumer<Throwable>() {
@Override
public void accept(Throwable t) throws Exception {
errorConsumer.accept(t);
@@ -315,10 +315,10 @@
@Override
public <O> Single<O> flatMap(final Function<V, Single<O>, Exception> function) {
- return new RxJavaSingle<>(impl.flatMap(new io.reactivex.functions.Function<V, SingleSource<O>>() {
+ return new RxJavaSingle<>(impl.flatMap(new io.reactivex.rxjava3.functions.Function<V, SingleSource<O>>() {
@Override
public SingleSource<O> apply(V t) throws Exception {
- return io.reactivex.Single.fromPublisher(function.apply(t));
+ return io.reactivex.rxjava3.core.Single.fromPublisher(function.apply(t));
}
}));
}
@@ -326,10 +326,10 @@
@Override
public Single<V> onErrorResumeWith(final Function<Throwable, Single<V>, Exception> function) {
return new RxJavaSingle<>(
- impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, SingleSource<V>>() {
+ impl.onErrorResumeNext(new io.reactivex.rxjava3.functions.Function<Throwable, SingleSource<V>>() {
@Override
public SingleSource<V> apply(Throwable error) throws Exception {
- return io.reactivex.Single.fromPublisher(function.apply(error));
+ return io.reactivex.rxjava3.core.Single.fromPublisher(function.apply(error));
}
}));
}
@@ -337,9 +337,9 @@
private static final class RxJavaCompletable implements Completable {
- private final io.reactivex.Completable impl;
+ private final io.reactivex.rxjava3.core.Completable impl;
- RxJavaCompletable(io.reactivex.Completable impl) {
+ RxJavaCompletable(io.reactivex.rxjava3.core.Completable impl) {
this.impl = impl;
}
@@ -356,22 +356,22 @@
@Override
public Completable onErrorResumeWith(final Function<Throwable, Completable, Exception> function) {
return new RxJavaCompletable(
- impl.onErrorResumeNext(new io.reactivex.functions.Function<Throwable, CompletableSource>() {
+ impl.onErrorResumeNext(new io.reactivex.rxjava3.functions.Function<Throwable, CompletableSource>() {
@Override
public CompletableSource apply(Throwable error) throws Exception {
- return io.reactivex.Completable.fromPublisher(function.apply(error));
+ return io.reactivex.rxjava3.core.Completable.fromPublisher(function.apply(error));
}
}));
}
@Override
public void subscribe(final Action completeAction, final Consumer<Throwable> errorConsumer) {
- impl.subscribe(new io.reactivex.functions.Action() {
+ impl.subscribe(new io.reactivex.rxjava3.functions.Action() {
@Override
public void run() throws Exception {
completeAction.run();
}
- }, new io.reactivex.functions.Consumer<Throwable>() {
+ }, new io.reactivex.rxjava3.functions.Consumer<Throwable>() {
@Override
public void accept(final Throwable error) throws Exception {
errorConsumer.accept(error);
@@ -381,7 +381,7 @@
@Override
public Completable doAfterTerminate(final Action onTerminate) {
- return new RxJavaCompletable(impl.doAfterTerminate(new io.reactivex.functions.Action() {
+ return new RxJavaCompletable(impl.doAfterTerminate(new io.reactivex.rxjava3.functions.Action() {
@Override
public void run() throws Exception {
onTerminate.run();
diff --git a/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java b/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java
index 0cb33bf..4edd2a2 100644
--- a/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java
+++ b/opendj-core/src/main/java/com/forgerock/reactive/ServerConnectionFactoryAdapter.java
@@ -59,10 +59,10 @@
import org.forgerock.util.Function;
import org.forgerock.util.promise.RuntimeExceptionHandler;
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.rxjava3.core.BackpressureStrategy;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.FlowableEmitter;
+import io.reactivex.rxjava3.core.FlowableOnSubscribe;
/**
* Adapt a {@link ServerConnectionFactory} to a {@link Function} compatible with
diff --git a/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java b/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java
new file mode 100644
index 0000000..2d4f589
--- /dev/null
+++ b/opendj-core/src/main/java/org/forgerock/opendj/ldap/controls/TransactionSpecificationRequestControl.java
@@ -0,0 +1,82 @@
+/*
+ * 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 2025 3A Systems,LLC.
+ */
+package org.forgerock.opendj.ldap.controls;
+
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.util.Reject;
+
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_TRANSACTION_ID_CONTROL_BAD_OID;
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_TRANSACTION_ID_CONTROL_DECODE_NULL;
+
+public class TransactionSpecificationRequestControl implements Control{
+
+ public final static String OID="1.3.6.1.1.21.2";
+
+ final String transactionId;
+ public TransactionSpecificationRequestControl(String transactionId) {
+ Reject.ifNull(transactionId);
+ this.transactionId = transactionId;
+ }
+
+ @Override
+ public String getOID() {
+ return OID;
+ }
+
+ @Override
+ public ByteString getValue() {
+ return ByteString.valueOfUtf8(transactionId);
+ }
+
+ @Override
+ public boolean hasValue() {
+ return true;
+ }
+
+ @Override
+ public boolean isCritical() {
+ return true;
+ }
+
+ public static final ControlDecoder<TransactionSpecificationRequestControl> DECODER = new ControlDecoder<TransactionSpecificationRequestControl>() {
+ @Override
+ public TransactionSpecificationRequestControl decodeControl(final Control control, final DecodeOptions options)
+ throws DecodeException {
+ Reject.ifNull(control);
+
+ if (control instanceof TransactionSpecificationRequestControl) {
+ return (TransactionSpecificationRequestControl) control;
+ }
+
+ if (!control.getOID().equals(OID)) {
+ throw DecodeException.error(ERR_TRANSACTION_ID_CONTROL_BAD_OID.get(control.getOID(), OID));
+ }
+
+ if (!control.hasValue()) {
+ throw DecodeException.error(ERR_TRANSACTION_ID_CONTROL_DECODE_NULL.get());
+ }
+
+ return new TransactionSpecificationRequestControl(control.getValue().toString());
+ }
+
+ @Override
+ public String getOID() {
+ return OID;
+ }
+ };
+}
diff --git a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
index 09085e4..c34d851 100644
--- a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
+++ b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/addrate.template
@@ -2,6 +2,8 @@
define maildomain=example.com
branch: [suffix]
+objectClass: top
+objectClass: domain
branch: ou=People,[suffix]
subordinateTemplate: person
diff --git a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
index 0f2c423..6d3074f 100644
--- a/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
+++ b/opendj-core/src/main/resources/org/forgerock/opendj/ldif/people_and_groups.template
@@ -5,6 +5,8 @@
define numgroup=5
branch: [suffix]
+objectClass: top
+objectClass: domain
subordinateTemplate: ous:[numous]
template: ous
@@ -59,4 +61,4 @@
rdnAttr: cn
objectClass: top
objectClass: groupOfNames
-cn: Group_<sequential:1>
\ No newline at end of file
+cn: Group_<sequential:1>
diff --git a/opendj-doc-generated-ref/pom.xml b/opendj-doc-generated-ref/pom.xml
index ccf7249..380302b 100644
--- a/opendj-doc-generated-ref/pom.xml
+++ b/opendj-doc-generated-ref/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-doc-generated-ref</artifactId>
@@ -656,7 +656,7 @@
<goal>run</goal>
</goals>
<configuration>
- <tasks>
+ <target>
<copy todir="${project.build.directory}/asciidoc/source/man-pages">
<fileset dir="${basedir}/src/main/asciidoc/man-pages" includes="**/*" />
</copy>
@@ -667,7 +667,7 @@
<delete>
<fileset dir="${project.build.directory}/asciidoc/source/man-pages" includes="man-*-subcommands-ref*" />
</delete>
- </tasks>
+ </target>
</configuration>
</execution>
<execution>
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc b/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc
index 5d3707f..926f3b9 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-standards.adoc
@@ -24,7 +24,7 @@
[#appendix-standards]
== Standards, RFCs, & Internet-Drafts
-OpenDJ 3.5 software implements the following RFCs, Internet-Drafts, and standards:
+OpenDJ software implements the following RFCs, Internet-Drafts, and standards:
--
[#rfc1274]
@@ -396,6 +396,13 @@
+
Describes the Lightweight Directory Access Protocol (LDAP) / X.500 'entryDN' operational attribute, that provides a copy of the entry's distinguished name for use in attribute value assertions.
+[#rfc5805]
+link:http://tools.ietf.org/html/rfc5805[RFC 5805: Lightweight Directory Access Protocol (LDAP) Transactions, window=\_top]::
++
+Lightweight Directory Access Protocol (LDAP) update operations, such as Add, Delete, and Modify operations, have atomic, consistency,
+isolation, durability (ACID) properties. Each of these update operations act upon an entry. It is often desirable to update two or
+more entries in a single unit of interaction, a transaction.
+
[#fips180-1]
link:http://www.itl.nist.gov/fipspubs/fip180-1.htm[FIPS 180-1: Secure Hash Standard (SHA-1), window=\_top]::
+
diff --git a/opendj-doc-maven-plugin/pom.xml b/opendj-doc-maven-plugin/pom.xml
index 022ece3..2b013c7 100644
--- a/opendj-doc-maven-plugin/pom.xml
+++ b/opendj-doc-maven-plugin/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-doc-maven-plugin</artifactId>
@@ -54,19 +54,25 @@
<dependency>
<groupId>org.twdata.maven</groupId>
<artifactId>mojo-executor</artifactId>
- <version>2.2.0</version>
+ <version>2.4.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.qdox</groupId>
<artifactId>qdox</artifactId>
- <version>2.0-M3</version>
+ <version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
- <version>3.6.0</version>
+ <version>3.15.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>3.9.9</version>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/opendj-dsml-servlet/pom.xml b/opendj-dsml-servlet/pom.xml
index 4224925..423f222 100644
--- a/opendj-dsml-servlet/pom.xml
+++ b/opendj-dsml-servlet/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-dsml-servlet</artifactId>
@@ -41,7 +41,6 @@
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
- <version>6.0.0</version>
<scope>provided</scope>
</dependency>
@@ -79,24 +78,24 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
-
+
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>4.0.3</version>
<type>pom</type>
</dependency>
-
+
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>3.0.1</version>
</dependency>
-
+
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
- <version>4.0.1</version>
+ <version>4.0.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
diff --git a/opendj-embedded-server-examples/pom.xml b/opendj-embedded-server-examples/pom.xml
index f85cac3..5180fb0 100644
--- a/opendj-embedded-server-examples/pom.xml
+++ b/opendj-embedded-server-examples/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-embedded-server-examples</artifactId>
diff --git a/opendj-embedded/pom.xml b/opendj-embedded/pom.xml
index 5ffb5ee..73ff62a 100644
--- a/opendj-embedded/pom.xml
+++ b/opendj-embedded/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<name>OpenDJ Embedded Server</name>
<artifactId>opendj-embedded</artifactId>
@@ -38,7 +38,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
- <version>1.5.13</version>
+ <version>1.5.18</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
@@ -49,7 +49,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
- <version>1.5.13</version>
+ <version>1.5.18</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
diff --git a/opendj-grizzly/pom.xml b/opendj-grizzly/pom.xml
index 7a6e175..2eff4ff 100644
--- a/opendj-grizzly/pom.xml
+++ b/opendj-grizzly/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-grizzly</artifactId>
@@ -51,6 +51,10 @@
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>io.reactivex.rxjava3</groupId>
+ <artifactId>rxjava</artifactId>
+ </exclusion>
</exclusions>
</dependency>
@@ -62,7 +66,6 @@
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-framework</artifactId>
- <version>${grizzly-framework.version}</version>
</dependency>
<dependency>
@@ -138,10 +141,6 @@
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- </plugin>
</plugins>
</build>
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
index 0af9bc6..d557f6c 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LDAPServerFilter.java
@@ -85,8 +85,8 @@
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Stream;
-import io.reactivex.exceptions.OnErrorNotImplementedException;
-import io.reactivex.internal.util.BackpressureHelper;
+import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
+import org.openidentityplatform.rxjava3.internal.util.BackpressureHelper;
/**
* Grizzly filter implementation for decoding LDAP requests and handling server side logic for SSL and SASL operations
@@ -390,7 +390,7 @@
return false;
}
SSLUtils.setSSLEngine(connection, sslEngine);
-
+
Properties props = System.getProperties();
// Workaround for PKCS11
@@ -398,7 +398,7 @@
if ("none".equalsIgnoreCase(keyStoreFile)) {
System.setProperty(SSLContextConfigurator.TRUST_STORE_FILE, "NONE");
}
-
+
SSLFilter sslFilter = new SSLFilter();
sslFilter.setHandshakeTimeout(getLongProperty("org.forgerock.opendj.grizzly.handshakeTimeout", 10000), TimeUnit.MILLISECONDS);
installFilter(startTls ? new StartTLSFilter(sslFilter) : sslFilter);
@@ -661,7 +661,7 @@
});
}
});
-
+
}
}
}
diff --git a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
index 138e371..47d90d8 100644
--- a/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
+++ b/opendj-grizzly/src/main/java/org/forgerock/opendj/grizzly/LdapResponseMessageWriter.java
@@ -18,10 +18,13 @@
import java.util.concurrent.CancellationException;
-import io.reactivex.exceptions.UndeliverableException;
+import io.reactivex.rxjava3.exceptions.UndeliverableException;
+
import org.forgerock.opendj.ldap.spi.LdapMessages.LdapResponseMessage;
+
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.Connection;
+
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
diff --git a/opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java b/opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java
new file mode 100644
index 0000000..2d3c0d4
--- /dev/null
+++ b/opendj-grizzly/src/main/java/org/openidentityplatform/rxjava3/internal/util/BackpressureHelper.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2016-present, RxJava Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
+ * the License for the specific language governing permissions and limitations under the License.
+ */
+
+package org.openidentityplatform.rxjava3.internal.util;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.plugins.RxJavaPlugins;
+
+/**
+ * Utility class to help with backpressure-related operations such as request aggregation.
+ */
+public final class BackpressureHelper {
+ /** Utility class. */
+ private BackpressureHelper() {
+ throw new IllegalStateException("No instances!");
+ }
+
+ /**
+ * Adds two long values and caps the sum at {@link Long#MAX_VALUE}.
+ * @param a the first value
+ * @param b the second value
+ * @return the sum capped at {@link Long#MAX_VALUE}
+ */
+ public static long addCap(long a, long b) {
+ long u = a + b;
+ if (u < 0L) {
+ return Long.MAX_VALUE;
+ }
+ return u;
+ }
+
+ /**
+ * Multiplies two long values and caps the product at {@link Long#MAX_VALUE}.
+ * @param a the first value
+ * @param b the second value
+ * @return the product capped at {@link Long#MAX_VALUE}
+ */
+ public static long multiplyCap(long a, long b) {
+ long u = a * b;
+ if (((a | b) >>> 31) != 0) {
+ if (u / a != b) {
+ return Long.MAX_VALUE;
+ }
+ }
+ return u;
+ }
+
+ /**
+ * Atomically adds the positive value n to the requested value in the {@link AtomicLong} and
+ * caps the result at {@link Long#MAX_VALUE} and returns the previous value.
+ * @param requested the {@code AtomicLong} holding the current requested value
+ * @param n the value to add, must be positive (not verified)
+ * @return the original value before the add
+ */
+ public static long add(@NonNull AtomicLong requested, long n) {
+ for (;;) {
+ long r = requested.get();
+ if (r == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+ long u = addCap(r, n);
+ if (requested.compareAndSet(r, u)) {
+ return r;
+ }
+ }
+ }
+
+ /**
+ * Atomically adds the positive value n to the requested value in the {@link AtomicLong} and
+ * caps the result at {@link Long#MAX_VALUE} and returns the previous value and
+ * considers {@link Long#MIN_VALUE} as a cancel indication (no addition then).
+ * @param requested the {@code AtomicLong} holding the current requested value
+ * @param n the value to add, must be positive (not verified)
+ * @return the original value before the add
+ */
+ public static long addCancel(@NonNull AtomicLong requested, long n) {
+ for (;;) {
+ long r = requested.get();
+ if (r == Long.MIN_VALUE) {
+ return Long.MIN_VALUE;
+ }
+ if (r == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+ long u = addCap(r, n);
+ if (requested.compareAndSet(r, u)) {
+ return r;
+ }
+ }
+ }
+
+ /**
+ * Atomically subtract the given number (positive, not validated) from the target field unless it contains {@link Long#MAX_VALUE}.
+ * @param requested the target field holding the current requested amount
+ * @param n the produced element count, positive (not validated)
+ * @return the new amount
+ */
+ public static long produced(@NonNull AtomicLong requested, long n) {
+ for (;;) {
+ long current = requested.get();
+ if (current == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+ long update = current - n;
+ if (update < 0L) {
+ RxJavaPlugins.onError(new IllegalStateException("More produced than requested: " + update));
+ update = 0L;
+ }
+ if (requested.compareAndSet(current, update)) {
+ return update;
+ }
+ }
+ }
+
+ /**
+ * Atomically subtract the given number (positive, not validated) from the target field if
+ * it doesn't contain {@link Long#MIN_VALUE} (indicating some cancelled state) or {@link Long#MAX_VALUE} (unbounded mode).
+ * @param requested the target field holding the current requested amount
+ * @param n the produced element count, positive (not validated)
+ * @return the new amount
+ */
+ public static long producedCancel(@NonNull AtomicLong requested, long n) {
+ for (;;) {
+ long current = requested.get();
+ if (current == Long.MIN_VALUE) {
+ return Long.MIN_VALUE;
+ }
+ if (current == Long.MAX_VALUE) {
+ return Long.MAX_VALUE;
+ }
+ long update = current - n;
+ if (update < 0L) {
+ RxJavaPlugins.onError(new IllegalStateException("More produced than requested: " + update));
+ update = 0L;
+ }
+ if (requested.compareAndSet(current, update)) {
+ return update;
+ }
+ }
+ }
+}
diff --git a/opendj-ldap-sdk-examples/pom.xml b/opendj-ldap-sdk-examples/pom.xml
index 8c8b6e3..6417083 100644
--- a/opendj-ldap-sdk-examples/pom.xml
+++ b/opendj-ldap-sdk-examples/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-ldap-sdk-examples</artifactId>
diff --git a/opendj-ldap-toolkit/pom.xml b/opendj-ldap-toolkit/pom.xml
index 42744ea..e318807 100644
--- a/opendj-ldap-toolkit/pom.xml
+++ b/opendj-ldap-toolkit/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-ldap-toolkit</artifactId>
diff --git a/opendj-legacy/pom.xml b/opendj-legacy/pom.xml
index 3a725d2..f95e2a0 100644
--- a/opendj-legacy/pom.xml
+++ b/opendj-legacy/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-legacy</artifactId>
<name>OpenDJ Legacy</name>
diff --git a/opendj-maven-plugin/pom.xml b/opendj-maven-plugin/pom.xml
index 641e251..7b66d61 100644
--- a/opendj-maven-plugin/pom.xml
+++ b/opendj-maven-plugin/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-maven-plugin</artifactId>
@@ -41,13 +41,19 @@
<dependency>
<groupId>org.twdata.maven</groupId>
<artifactId>mojo-executor</artifactId>
- <version>2.3.0</version>
+ <version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
- <version>3.6.0</version>
+ <version>3.15.1</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <version>3.9.9</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml
new file mode 100644
index 0000000..3631751
--- /dev/null
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/EndTransactionExtendedOperationHandlerConfiguration.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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 2025 3A Systems,LLC.
+ ! -->
+<adm:managed-object name="end-transaction-extended-operation-handler"
+ plural-name="end-transaction-extended-operation-handlers"
+ package="org.forgerock.opendj.server.config"
+ extends="extended-operation-handler"
+ xmlns:adm="http://opendj.forgerock.org/admin"
+ xmlns:ldap="http://opendj.forgerock.org/admin-ldap">
+ <adm:synopsis>
+ The
+ <adm:user-friendly-name />
+ An End Transaction Request is an LDAPMessage of CHOICE extendedReq
+ where the requestName is 1.3.6.1.1.21.3 and the requestValue is
+ present and contains a BER-encoded txnEndReq.
+
+ txnEndReq ::= SEQUENCE {
+ commit BOOLEAN DEFAULT TRUE,
+ identifier OCTET STRING }
+
+ A commit value of TRUE indicates a request to commit the transaction
+ identified by the identifier. A commit value of FALSE indicates a
+ request to abort the identified transaction.
+
+ An End Transaction Response is an LDAPMessage sent in response to a
+ End Transaction Request. Its response name is absent. The
+ responseValue when present contains a BER-encoded txnEndRes.
+
+ txnEndRes ::= SEQUENCE {
+ messageID MessageID OPTIONAL,
+ -- msgid associated with non-success resultCode
+ updatesControls SEQUENCE OF updateControls SEQUENCE {
+ messageID MessageID,
+ -- msgid associated with controls
+ controls Controls
+ } OPTIONAL
+ }
+ -- where MessageID and Controls are as specified in RFC 4511
+
+ The txnEndRes.messageID provides the message id of the update request
+ associated with a non-success response. txnEndRes.messageID is
+ absent when resultCode of the End Transaction Response is success
+ (0).
+ </adm:synopsis>
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:name>
+ ds-cfg-end-transaction-extended-operation-handler
+ </ldap:name>
+ <ldap:superior>ds-cfg-extended-operation-handler</ldap:superior>
+ </ldap:object-class>
+ </adm:profile>
+ <adm:property-override name="java-class" advanced="true">
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>
+ org.opends.server.extensions.EndTransactionExtendedOperation
+ </adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+</adm:managed-object>
diff --git a/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml
new file mode 100644
index 0000000..e70290a
--- /dev/null
+++ b/opendj-maven-plugin/src/main/resources/config/xml/org/forgerock/opendj/server/config/StartTransactionExtendedOperationHandlerConfiguration.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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 2025 3A Systems,LLC.
+ ! -->
+<adm:managed-object name="start-transaction-extended-operation-handler"
+ plural-name="start-transaction-extended-operation-handlers"
+ package="org.forgerock.opendj.server.config"
+ extends="extended-operation-handler"
+ xmlns:adm="http://opendj.forgerock.org/admin"
+ xmlns:ldap="http://opendj.forgerock.org/admin-ldap">
+ <adm:synopsis>
+ The
+ <adm:user-friendly-name />
+ A client wishing to perform a sequence of directory updates as a
+ transaction issues a Start Transaction Request. A server that is
+ willing and able to support transactions responds to this request
+ with a Start Transaction Response providing a transaction identifier
+ and with a resultCode of success (0). Otherwise, the server responds
+ with a Start Transaction Response with a resultCode other than
+ success indicating the nature of the failure.
+
+ The transaction identifier provided upon successful start of a
+ transaction is used in subsequent protocol messages to identify this
+ transaction.
+ </adm:synopsis>
+ <adm:profile name="ldap">
+ <ldap:object-class>
+ <ldap:name>
+ ds-cfg-start-transaction-extended-operation-handler
+ </ldap:name>
+ <ldap:superior>ds-cfg-extended-operation-handler</ldap:superior>
+ </ldap:object-class>
+ </adm:profile>
+ <adm:property-override name="java-class" advanced="true">
+ <adm:default-behavior>
+ <adm:defined>
+ <adm:value>
+ org.opends.server.extensions.StartTransactionExtendedOperation
+ </adm:value>
+ </adm:defined>
+ </adm:default-behavior>
+ </adm:property-override>
+</adm:managed-object>
diff --git a/opendj-openidm-account-change-notification-handler/pom.xml b/opendj-openidm-account-change-notification-handler/pom.xml
index 825bed4..a0fff3a 100644
--- a/opendj-openidm-account-change-notification-handler/pom.xml
+++ b/opendj-openidm-account-change-notification-handler/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-openidm-account-change-notification-handler</artifactId>
<name>OpenDJ account change notification handler for OpenIDM</name>
diff --git a/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml b/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml
index 2b691ba..d135f74 100644
--- a/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml
+++ b/opendj-packages/opendj-deb/opendj-deb-standard/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-deb</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-deb-standard</artifactId>
diff --git a/opendj-packages/opendj-deb/pom.xml b/opendj-packages/opendj-deb/pom.xml
index 31e7534..3809a10 100644
--- a/opendj-packages/opendj-deb/pom.xml
+++ b/opendj-packages/opendj-deb/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-packages</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<profiles>
diff --git a/opendj-packages/opendj-docker/pom.xml b/opendj-packages/opendj-docker/pom.xml
index ff2c653..4a32f7a 100644
--- a/opendj-packages/opendj-docker/pom.xml
+++ b/opendj-packages/opendj-docker/pom.xml
@@ -22,7 +22,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-packages</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<profiles>
@@ -74,16 +74,16 @@
<execution>
<phase>prepare-package</phase>
<configuration>
- <tasks>
+ <target>
<zip basedir="${project.build.directory}" destfile="${project.build.directory}/Dockerfile.zip" includes="opendj-docker/**">
<fileset dir="${basedir}">
- <include name="Dockerfile*" />
- <include name="../opendj-openshift-template/*.yaml" />
+ <include name="Dockerfile*" />
+ <include name="../opendj-openshift-template/*.yaml" />
<include name="bootstrap/**" />
<include name="run.sh" />
</fileset>
</zip>
- </tasks>
+ </target>
</configuration>
<goals>
<goal>run</goal>
diff --git a/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml b/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml
index 5650712..f4b67a9 100644
--- a/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml
+++ b/opendj-packages/opendj-msi/opendj-msi-standard/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-msi</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-msi-standard</artifactId>
diff --git a/opendj-packages/opendj-msi/pom.xml b/opendj-packages/opendj-msi/pom.xml
index df36b20..d019a62 100644
--- a/opendj-packages/opendj-msi/pom.xml
+++ b/opendj-packages/opendj-msi/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-packages</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-msi</artifactId>
diff --git a/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml b/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml
index 32af268..d793364 100644
--- a/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml
+++ b/opendj-packages/opendj-rpm/opendj-rpm-standard/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-rpm</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-rpm-standard</artifactId>
diff --git a/opendj-packages/opendj-rpm/pom.xml b/opendj-packages/opendj-rpm/pom.xml
index 32e70d0..52d416a 100644
--- a/opendj-packages/opendj-rpm/pom.xml
+++ b/opendj-packages/opendj-rpm/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-packages</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<packaging>pom</packaging>
diff --git a/opendj-packages/opendj-svr4/pom.xml b/opendj-packages/opendj-svr4/pom.xml
index 25bf9e1..f59b90a 100644
--- a/opendj-packages/opendj-svr4/pom.xml
+++ b/opendj-packages/opendj-svr4/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-packages</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-svr4</artifactId>
diff --git a/opendj-packages/pom.xml b/opendj-packages/pom.xml
index 39abc6d..9d15d09 100644
--- a/opendj-packages/pom.xml
+++ b/opendj-packages/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-packages</artifactId>
diff --git a/opendj-rest2ldap-servlet/pom.xml b/opendj-rest2ldap-servlet/pom.xml
index 53c3611..89b34f1 100644
--- a/opendj-rest2ldap-servlet/pom.xml
+++ b/opendj-rest2ldap-servlet/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-rest2ldap-servlet</artifactId>
diff --git a/opendj-rest2ldap/pom.xml b/opendj-rest2ldap/pom.xml
index a2a19b5..ca291fe 100644
--- a/opendj-rest2ldap/pom.xml
+++ b/opendj-rest2ldap/pom.xml
@@ -20,7 +20,7 @@
<parent>
<artifactId>opendj-parent</artifactId>
<groupId>org.openidentityplatform.opendj</groupId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-rest2ldap</artifactId>
diff --git a/opendj-server-example-plugin/pom.xml b/opendj-server-example-plugin/pom.xml
index b82baf33..f09bfd2 100644
--- a/opendj-server-example-plugin/pom.xml
+++ b/opendj-server-example-plugin/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-server-example-plugin</artifactId>
<name>OpenDJ Server Example Plugin</name>
@@ -38,7 +38,6 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
- <version>3.3.2</version>
</dependency>
</dependencies>
<build><finalName>${project.groupId}.${project.artifactId}</finalName>
@@ -80,10 +79,6 @@
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
diff --git a/opendj-server-legacy/pom.xml b/opendj-server-legacy/pom.xml
index b9a9356..27c7334 100644
--- a/opendj-server-legacy/pom.xml
+++ b/opendj-server-legacy/pom.xml
@@ -21,7 +21,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-server-legacy</artifactId>
<packaging>jar</packaging>
@@ -266,7 +266,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
- <version>42.7.5</version>
+ <version>[42.7.7,)</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
diff --git a/opendj-server-legacy/resource/config/config.ldif b/opendj-server-legacy/resource/config/config.ldif
index f836b18..0683e93 100644
--- a/opendj-server-legacy/resource/config/config.ldif
+++ b/opendj-server-legacy/resource/config/config.ldif
@@ -14,6 +14,7 @@
# Portions Copyright 2012-2014 Manuel Gaupp
# Portions Copyright 2010-2016 ForgeRock AS.
# Portions copyright 2015 Edan Idzerda
+# Portions Copyright 2025 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
@@ -603,6 +604,22 @@
ds-cfg-java-class: org.opends.server.extensions.WhoAmIExtendedOperation
ds-cfg-enabled: true
+dn: cn=Start Transaction,cn=Extended Operations,cn=config
+objectClass: top
+objectClass: ds-cfg-extended-operation-handler
+objectClass: ds-cfg-start-transaction-extended-operation-handler
+cn: Start Transaction
+ds-cfg-java-class: org.opends.server.extensions.StartTransactionExtendedOperation
+ds-cfg-enabled: true
+
+dn: cn=End Transaction,cn=Extended Operations,cn=config
+objectClass: top
+objectClass: ds-cfg-extended-operation-handler
+objectClass: ds-cfg-end-transaction-extended-operation-handler
+cn: End Transaction
+ds-cfg-java-class: org.opends.server.extensions.EndTransactionExtendedOperation
+ds-cfg-enabled: true
+
dn: cn=Group Implementations,cn=config
objectClass: top
objectClass: ds-cfg-branch
diff --git a/opendj-server-legacy/resource/schema/02-config.ldif b/opendj-server-legacy/resource/schema/02-config.ldif
index a950d83..2f8cc52 100644
--- a/opendj-server-legacy/resource/schema/02-config.ldif
+++ b/opendj-server-legacy/resource/schema/02-config.ldif
@@ -6265,4 +6265,13 @@
STRUCTURAL
MAY ds-cfg-pbkdf2-iterations
X-ORIGIN 'OpenDJ Directory Server' )
-
\ No newline at end of file
+objectClasses: ( 1.3.6.1.4.1.60142.1.2.1
+ NAME 'ds-cfg-start-transaction-extended-operation-handler'
+ SUP ds-cfg-extended-operation-handler
+ STRUCTURAL
+ X-ORIGIN 'OpenDS Directory Server' )
+objectClasses: ( 1.3.6.1.4.1.60142.1.2.2
+ NAME 'ds-cfg-end-transaction-extended-operation-handler'
+ SUP ds-cfg-extended-operation-handler
+ STRUCTURAL
+ X-ORIGIN 'OpenDS Directory Server' )
\ No newline at end of file
diff --git a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
index ab47f5f..7779dc7 100644
--- a/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
+++ b/opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPClientConnection2.java
@@ -117,10 +117,10 @@
import com.forgerock.reactive.ReactiveHandler;
import com.forgerock.reactive.Stream;
-import io.reactivex.BackpressureStrategy;
-import io.reactivex.Flowable;
-import io.reactivex.FlowableEmitter;
-import io.reactivex.FlowableOnSubscribe;
+import io.reactivex.rxjava3.core.BackpressureStrategy;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.FlowableEmitter;
+import io.reactivex.rxjava3.core.FlowableOnSubscribe;
/**
* This class defines an LDAP client connection, which is a type of client connection that will be accepted by an
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java b/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java
index 7487b3d..a891fee 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/api/ClientConnection.java
@@ -13,6 +13,7 @@
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2025 3A Systems, LLC.
*/
package org.opends.server.api;
@@ -20,11 +21,8 @@
import java.nio.channels.ByteChannel;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -53,6 +51,7 @@
import org.opends.server.types.Privilege;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchResultReference;
+import org.opends.server.types.operation.RollbackOperation;
import org.opends.server.util.TimeThread;
import static org.opends.messages.CoreMessages.*;
@@ -1576,4 +1575,47 @@
{
return getConnectionID() < 0;
}
+
+ public class Transaction {
+ final String transactionId=UUID.randomUUID().toString().toLowerCase();
+
+ public Transaction() {
+ transactions.put(getTransactionId(),this);
+ }
+
+ public String getTransactionId() {
+ return transactionId;
+ }
+
+ final Queue<Operation> waiting=new LinkedList<>();
+ public void add(Operation operation) {
+ waiting.add(operation);
+ }
+
+ public Queue<Operation> getWaiting() {
+ return waiting;
+ }
+
+ public void clear() {
+ transactions.remove(getTransactionId());
+ }
+ final Deque<RollbackOperation> completed =new ArrayDeque<>();
+ public void success(RollbackOperation operation) {
+ completed.add(operation);
+ }
+
+ public Deque<RollbackOperation> getCompleted() {
+ return completed;
+ }
+ }
+
+ Map<String,Transaction> transactions=new ConcurrentHashMap<>();
+
+ public Transaction startTransaction() {
+ return new Transaction();
+ }
+
+ public Transaction getTransaction(String id) {
+ return transactions.get(id);
+ }
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java
new file mode 100644
index 0000000..9ebb554
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/EndTransactionExtendedOperation.java
@@ -0,0 +1,131 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package org.opends.server.extensions;
+
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedRequest;
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedResult;
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.io.ASN1;
+import org.forgerock.opendj.io.ASN1Reader;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.server.config.server.EndTransactionExtendedOperationHandlerCfg;
+import org.opends.server.api.ClientConnection;
+import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.core.ExtendedOperation;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.Operation;
+import org.opends.server.types.operation.RollbackOperation;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.Queue;
+
+import static com.forgerock.opendj.ldap.CoreMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
+import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
+
+
+public class EndTransactionExtendedOperation extends ExtendedOperationHandler<EndTransactionExtendedOperationHandlerCfg>
+{
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+ public EndTransactionExtendedOperation()
+ {
+ super();
+ }
+
+ @Override
+ public void initializeExtendedOperationHandler(EndTransactionExtendedOperationHandlerCfg config) throws ConfigException, InitializationException
+ {
+ super.initializeExtendedOperationHandler(config);
+ }
+
+ @Override
+ public void processExtendedOperation(ExtendedOperation operation)
+ {
+ final EndTransactionExtendedRequest request =new EndTransactionExtendedRequest();
+ if (operation.getRequestValue()!= null) {
+ final ASN1Reader reader = ASN1.getReader(operation.getRequestValue());
+ try {
+ reader.readStartSequence();
+ if (reader.hasNextElement()&& (reader.peekType() == ASN1.UNIVERSAL_BOOLEAN_TYPE)) {
+ request.setCommit(reader.readBoolean());
+ }
+ request.setTransactionID(reader.readOctetStringAsString());
+ reader.readEndSequence();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ final ClientConnection.Transaction trn=operation.getClientConnection().getTransaction(request.getTransactionID());
+ if (trn==null) {
+ operation.setResultCode(ResultCode.CANCELLED);
+ operation.appendErrorMessage(LocalizableMessage.raw("unknown transactionId="+request.getTransactionID()));
+ return;
+ }
+
+ final EndTransactionExtendedResult res=EndTransactionExtendedResult.newResult(ResultCode.SUCCESS);
+ operation.setResultCode(res.getResultCode());
+ Operation currentOperation=null;
+ try {
+ while((currentOperation=trn.getWaiting().poll())!=null) {
+ if (request.isCommit()) {
+ currentOperation.run();
+ if (!ResultCode.SUCCESS.equals(currentOperation.getResultCode())) {
+ throw new InterruptedException();
+ }
+ currentOperation.operationCompleted();
+ //res.success(currentOperation.getMessageID(),currentOperation.getResponseControls());
+ }
+ }
+ }catch (Throwable e){
+ res.setFailedMessageID(currentOperation.getMessageID());
+ operation.setResultCode(currentOperation.getResultCode());
+ operation.setErrorMessage(currentOperation.getErrorMessage());
+ //rollback
+ RollbackOperation cancelOperation=null;
+ while((cancelOperation=trn.getCompleted().pollLast())!=null) {
+ try {
+ cancelOperation.rollback();
+ }catch (Throwable e2){
+ throw new RuntimeException("rollback error",e2);
+ }
+ }
+ }finally {
+ trn.clear();
+ }
+ operation.setResponseOID(res.getOID());
+ operation.setResponseValue(res.getValue());
+ }
+
+ @Override
+ public String getExtendedOperationOID()
+ {
+ return EndTransactionExtendedRequest.END_TRANSACTION_REQUEST_OID;
+ }
+
+ @Override
+ public String getExtendedOperationName()
+ {
+ return "End Transaction";
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java
new file mode 100644
index 0000000..b7fb0a3
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/StartTransactionExtendedOperation.java
@@ -0,0 +1,66 @@
+/*
+ * 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 2025 3A Systems, LLC
+ */
+package org.opends.server.extensions;
+
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedRequest;
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedResult;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.server.config.server.StartTransactionExtendedOperationHandlerCfg;
+import org.opends.server.api.ExtendedOperationHandler;
+import org.opends.server.core.ExtendedOperation;
+import org.opends.server.types.InitializationException;
+
+public class StartTransactionExtendedOperation extends ExtendedOperationHandler<StartTransactionExtendedOperationHandlerCfg>
+{
+ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+
+ public StartTransactionExtendedOperation()
+ {
+ super();
+ }
+
+ @Override
+ public void initializeExtendedOperationHandler(StartTransactionExtendedOperationHandlerCfg config) throws ConfigException, InitializationException
+ {
+ super.initializeExtendedOperationHandler(config);
+ }
+
+ @Override
+ public void processExtendedOperation(ExtendedOperation operation)
+ {
+ final StartTransactionExtendedResult res=StartTransactionExtendedResult
+ .newResult(ResultCode.SUCCESS)
+ .setTransactionID(operation.getClientConnection().startTransaction().getTransactionId());
+
+ operation.setResponseOID(res.getOID());
+ operation.setResponseValue(res.getValue());
+ operation.setResultCode(res.getResultCode());
+ }
+
+ @Override
+ public String getExtendedOperationOID()
+ {
+ return StartTransactionExtendedRequest.START_TRANSACTION_REQUEST_OID;
+ }
+
+ @Override
+ public String getExtendedOperationName()
+ {
+ return "Start Transaction";
+ }
+}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java
index d88bdca..1ceae70 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/TraditionalWorkerThread.java
@@ -13,15 +13,19 @@
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2025 3A Systems, LLC.
*/
package org.opends.server.extensions;
import java.util.Map;
import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.opends.server.api.ClientConnection;
import org.opends.server.api.DirectoryThread;
import org.opends.server.core.DirectoryServer;
import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.opends.server.types.AbstractOperation;
import org.opends.server.types.CancelRequest;
import org.opends.server.types.DisconnectReason;
import org.opends.server.types.Operation;
@@ -145,8 +149,29 @@
{
// The operation is not null, so process it. Make sure that when
// processing is complete.
- operation.run();
- operation.operationCompleted();
+
+ //check has transactionId control
+ ClientConnection.Transaction transaction=null;
+ if (operation instanceof AbstractOperation) {
+ String transactionId = ((AbstractOperation) operation).getTransactionId();
+ if (transactionId!=null){
+ transaction=operation.getClientConnection().getTransaction(transactionId);
+ if (transaction==null){ //unknown transactionId
+ operation.setResultCode(ResultCode.CANCELLED);
+ operation.appendErrorMessage(LocalizableMessage.raw("unknown transactionId="+transactionId));
+ operation.getClientConnection().sendResponse(operation);
+ continue;
+ }
+ }
+ }
+ if (transaction==null) { //run
+ operation.run();
+ operation.operationCompleted();
+ }else { //suspend for commit
+ transaction.add(operation);
+ operation.setResultCode(ResultCode.SUCCESS);
+ operation.getClientConnection().sendResponse(operation);
+ }
}
}
catch (Throwable t)
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java
index 61ee9f2..5f5c67c 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/AbstractOperation.java
@@ -13,6 +13,7 @@
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2023-2025 3A Systems, LLC.
*/
package org.opends.server.types;
@@ -29,6 +30,7 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
import org.forgerock.util.Reject;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.plugin.PluginResult.OperationResult;
@@ -721,4 +723,16 @@
}
return true;
}
+
+ public String getTransactionId() {
+ for (Control control : getRequestControls()) {
+ if (control.getOID().equals(TransactionSpecificationRequestControl.OID)) {
+ if ((control instanceof LDAPControl) && ((LDAPControl)control).getValue()!=null){
+ return ((LDAPControl) control).getValue().toString();
+ }
+ }
+ }
+ return null;
+ }
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java
new file mode 100644
index 0000000..cdeb151
--- /dev/null
+++ b/opendj-server-legacy/src/main/java/org/opends/server/types/operation/RollbackOperation.java
@@ -0,0 +1,26 @@
+/*
+ * 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 2025 3A Systems,LLC.
+ */
+package org.opends.server.types.operation;
+
+import org.opends.server.api.ClientConnection;
+import org.opends.server.types.CanceledOperationException;
+import org.opends.server.types.DirectoryException;
+
+public interface RollbackOperation
+{
+ void rollback() throws CanceledOperationException, DirectoryException;
+}
+
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
index 1b86927..03a1e3b 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendAddOperation.java
@@ -13,7 +13,7 @@
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
- * Portions copyright 2024 3A Systems,LLC.
+ * Portions Copyright 2024-2025 3A Systems,LLC.
*/
package org.opends.server.workflowelement.localbackend;
@@ -39,6 +39,7 @@
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.ObjectClass;
import org.forgerock.opendj.ldap.schema.Syntax;
@@ -61,6 +62,7 @@
import org.opends.server.core.PasswordPolicy;
import org.opends.server.core.PersistentSearch;
import org.opends.server.core.ServerContext;
+import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.Attribute;
@@ -73,10 +75,7 @@
import org.opends.server.types.LockManager.DNLock;
import org.opends.server.types.Privilege;
import org.opends.server.types.SearchFilter;
-import org.opends.server.types.operation.PostOperationAddOperation;
-import org.opends.server.types.operation.PostResponseAddOperation;
-import org.opends.server.types.operation.PostSynchronizationAddOperation;
-import org.opends.server.types.operation.PreOperationAddOperation;
+import org.opends.server.types.operation.*;
import org.opends.server.util.TimeThread;
/**
@@ -86,7 +85,7 @@
public class LocalBackendAddOperation
extends AddOperationWrapper
implements PreOperationAddOperation, PostOperationAddOperation,
- PostResponseAddOperation, PostSynchronizationAddOperation
+ PostResponseAddOperation, PostSynchronizationAddOperation,RollbackOperation
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -470,6 +469,9 @@
}
backend.addEntry(entry, this);
+ if (trx!=null) {
+ trx.success(this);
+ }
}
LocalBackendWorkflowElement.addPostReadResponse(this, postReadRequest,
@@ -496,7 +498,10 @@
}
}
-
+ @Override
+ public void rollback() throws CanceledOperationException, DirectoryException {
+ backend.deleteEntry(entryDN,null);
+ }
private void processSynchPostOperationPlugins()
{
@@ -968,6 +973,10 @@
{
RelaxRulesControlRequested = true;
}
+ else if (TransactionSpecificationRequestControl.OID.equals(oid))
+ {
+ trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+ }
else if (c.isCritical() && !backend.supportsControl(oid))
{
throw newDirectoryException(entryDN, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -975,9 +984,11 @@
}
}
}
+ ClientConnection.Transaction trx=null;
private AccessControlHandler<?> getAccessControlHandler()
{
return AccessControlConfigManager.getInstance().getAccessControlHandler();
}
+
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
index 7fb633e..0a10664 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendDeleteOperation.java
@@ -13,7 +13,7 @@
*
* Copyright 2008-2009 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
- * Portions Copyright 2022-2024 3A Systems, LLC.
+ * Portions Copyright 2022-2025 3A Systems, LLC.
*/
package org.opends.server.workflowelement.localbackend;
@@ -23,6 +23,7 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.LocalBackend;
import org.opends.server.api.ClientConnection;
@@ -35,6 +36,7 @@
import org.opends.server.core.DeleteOperationWrapper;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.PersistentSearch;
+import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.types.CanceledOperationException;
import org.opends.server.types.Control;
import org.forgerock.opendj.ldap.DN;
@@ -43,10 +45,7 @@
import org.opends.server.types.LockManager.DNLock;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SynchronizationProviderResult;
-import org.opends.server.types.operation.PostOperationDeleteOperation;
-import org.opends.server.types.operation.PostResponseDeleteOperation;
-import org.opends.server.types.operation.PostSynchronizationDeleteOperation;
-import org.opends.server.types.operation.PreOperationDeleteOperation;
+import org.opends.server.types.operation.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.core.DirectoryServer.*;
@@ -63,7 +62,7 @@
extends DeleteOperationWrapper
implements PreOperationDeleteOperation, PostOperationDeleteOperation,
PostResponseDeleteOperation,
- PostSynchronizationDeleteOperation
+ PostSynchronizationDeleteOperation, RollbackOperation
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -299,6 +298,9 @@
return;
}
backend.deleteEntry(entryDN, this);
+ if (trx!=null) {
+ trx.success(this);
+ }
}
LocalBackendWorkflowElement.addPreReadResponse(this, preReadRequest, entry);
@@ -416,6 +418,10 @@
{
continue;
}
+ else if (TransactionSpecificationRequestControl.OID.equals(oid))
+ {
+ trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+ }
else if (c.isCritical() && !backend.supportsControl(oid))
{
throw newDirectoryException(entry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -423,6 +429,7 @@
}
}
}
+ ClientConnection.Transaction trx=null;
/**
* Handle conflict resolution.
@@ -488,4 +495,9 @@
}
return true;
}
+
+ @Override
+ public void rollback() throws CanceledOperationException, DirectoryException {
+ backend.addEntry(entry,null);
+ }
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
index a0a575d..effec13 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyDNOperation.java
@@ -13,7 +13,7 @@
*
* Copyright 2008-2010 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
- * Portions copyright 2024 3A Systems,LLC.
+ * Portions copyright 2024-2025 3A Systems,LLC.
*/
package org.opends.server.workflowelement.localbackend;
@@ -29,6 +29,7 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.opends.server.api.AccessControlHandler;
import org.opends.server.api.LocalBackend;
@@ -43,6 +44,7 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.ModifyDNOperationWrapper;
import org.opends.server.core.PersistentSearch;
+import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.types.Attribute;
import org.opends.server.types.Attributes;
import org.opends.server.types.CanceledOperationException;
@@ -54,10 +56,7 @@
import org.opends.server.types.Modification;
import org.forgerock.opendj.ldap.RDN;
import org.opends.server.types.SearchFilter;
-import org.opends.server.types.operation.PostOperationModifyDNOperation;
-import org.opends.server.types.operation.PostResponseModifyDNOperation;
-import org.opends.server.types.operation.PostSynchronizationModifyDNOperation;
-import org.opends.server.types.operation.PreOperationModifyDNOperation;
+import org.opends.server.types.operation.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.core.DirectoryServer.*;
@@ -75,7 +74,7 @@
implements PreOperationModifyDNOperation,
PostOperationModifyDNOperation,
PostResponseModifyDNOperation,
- PostSynchronizationModifyDNOperation
+ PostSynchronizationModifyDNOperation, RollbackOperation
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -439,6 +438,9 @@
return;
}
currentBackend.renameEntry(entryDN, newEntry, this);
+ if (trx!=null) {
+ trx.success(this);
+ }
}
// Attach the pre-read and/or post-read controls to the response if
@@ -576,6 +578,10 @@
{
continue;
}
+ else if (TransactionSpecificationRequestControl.OID.equals(oid))
+ {
+ trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+ }
else if (c.isCritical() && !backend.supportsControl(oid))
{
throw new DirectoryException(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -583,6 +589,7 @@
}
}
}
+ ClientConnection.Transaction trx=null;
private AccessControlHandler<?> getAccessControlHandler()
{
@@ -816,4 +823,9 @@
}
}
}
+
+ @Override
+ public void rollback() throws CanceledOperationException, DirectoryException {
+ backend.renameEntry(newEntry.getName(), currentEntry, this);
+ }
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
index 6995427..f2c7f78 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendModifyOperation.java
@@ -13,7 +13,7 @@
*
* Copyright 2008-2011 Sun Microsystems, Inc.
* Portions Copyright 2011-2016 ForgeRock AS.
- * Portions copyright 2024 3A Systems,LLC.
+ * Portions Copyright 2024-2025 3A Systems,LLC.
*/
package org.opends.server.workflowelement.localbackend;
@@ -35,6 +35,7 @@
import org.forgerock.opendj.ldap.RDN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.controls.RelaxRulesControl;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.schema.ObjectClass;
@@ -60,6 +61,7 @@
import org.opends.server.core.PasswordPolicy;
import org.opends.server.core.PasswordPolicyState;
import org.opends.server.core.PersistentSearch;
+import org.opends.server.protocols.ldap.LDAPControl;
import org.opends.server.schema.AuthPasswordSyntax;
import org.opends.server.schema.UserPasswordSyntax;
import org.opends.server.types.AcceptRejectWarn;
@@ -77,10 +79,7 @@
import org.opends.server.types.Privilege;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SynchronizationProviderResult;
-import org.opends.server.types.operation.PostOperationModifyOperation;
-import org.opends.server.types.operation.PostResponseModifyOperation;
-import org.opends.server.types.operation.PostSynchronizationModifyOperation;
-import org.opends.server.types.operation.PreOperationModifyOperation;
+import org.opends.server.types.operation.*;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.*;
@@ -96,7 +95,7 @@
extends ModifyOperationWrapper
implements PreOperationModifyOperation, PostOperationModifyOperation,
PostResponseModifyOperation,
- PostSynchronizationModifyOperation
+ PostSynchronizationModifyOperation, RollbackOperation
{
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
@@ -500,6 +499,9 @@
}
backend.replaceEntry(currentEntry, modifiedEntry, this);
+ if (trx!=null) {
+ trx.success(this);
+ }
if (isAuthnManagedLocally())
{
@@ -697,6 +699,10 @@
{
RelaxRulesControlRequested = true;
}
+ else if (TransactionSpecificationRequestControl.OID.equals(oid))
+ {
+ trx=getClientConnection().getTransaction(((LDAPControl)c).getValue().toString());
+ }
else if (c.isCritical() && !backend.supportsControl(oid))
{
throw newDirectoryException(currentEntry, ResultCode.UNAVAILABLE_CRITICAL_EXTENSION,
@@ -704,6 +710,7 @@
}
}
}
+ ClientConnection.Transaction trx=null;
private void processNonPasswordModifications() throws DirectoryException
{
@@ -1658,4 +1665,9 @@
}
}
}
+
+ @Override
+ public void rollback() throws CanceledOperationException, DirectoryException {
+ backend.replaceEntry(modifiedEntry,currentEntry, this);
+ }
}
diff --git a/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java b/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java
new file mode 100644
index 0000000..916f253
--- /dev/null
+++ b/opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/Rfc5808TestCase.java
@@ -0,0 +1,260 @@
+/*
+ * 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 2025 3A Systems, LLC.
+ */
+package org.openidentityplatform.opendj;
+
+
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedRequest;
+import com.forgerock.opendj.ldap.extensions.EndTransactionExtendedResult;
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedRequest;
+import org.forgerock.opendj.ldap.*;
+import org.forgerock.opendj.ldap.controls.TransactionSpecificationRequestControl;
+import org.forgerock.opendj.ldap.requests.*;
+import com.forgerock.opendj.ldap.extensions.StartTransactionExtendedResult;
+import org.forgerock.opendj.ldap.responses.Result;
+import org.opends.server.DirectoryServerTestCase;
+import org.opends.server.TestCaseUtils;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.forgerock.opendj.ldap.requests.Requests.newAddRequest;
+import static org.testng.Assert.assertThrows;
+
+@Test(sequential = true)
+public class Rfc5808TestCase extends DirectoryServerTestCase {
+ Connection connection;
+
+ @BeforeClass
+ public void startServer() throws Exception {
+ TestCaseUtils.startServer();
+ TestCaseUtils.initializeTestBackend(true);
+
+ final LDAPConnectionFactory factory =new LDAPConnectionFactory("localhost", TestCaseUtils.getServerLdapPort());
+ connection = factory.getConnection();
+ connection.bind("cn=Directory Manager", "password".toCharArray());
+ assertThat(connection.isValid()).isTrue();
+ }
+
+ @Test
+ public void test() throws LdapException {
+ //unknown transaction in TransactionSpecificationRequestControl
+ assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ AddRequest add=Requests
+ .newAddRequest("ou=People,o=test")
+ .addAttribute("objectClass", "top", "organizationalUnit")
+ .addAttribute("ou", "People")
+ .addControl(new TransactionSpecificationRequestControl("bad"))
+ ;
+ Result result = connection.add(add);
+ }
+ });
+
+ //unknown transaction in EndTransactionExtendedRequest
+ assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID("unknown").setCommit(true));
+ }
+ });
+ assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID("unknown"));
+ }
+ });
+
+ //commit
+ StartTransactionExtendedResult resStart=connection.extendedRequest(new StartTransactionExtendedRequest());
+ assertThat(resStart.isSuccess()).isTrue();
+ assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1");
+ String transactionID=resStart.getTransactionID();
+ assertThat(transactionID).isNotEmpty();
+
+ AddRequest add=Requests
+ .newAddRequest("ou=People,o=test")
+ .addAttribute("objectClass", "top", "organizationalUnit")
+ .addAttribute("ou", "People")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ Result result = connection.add(add);
+ assertThat(result.isSuccess()).isTrue();
+
+ add= Requests.newAddRequest("sn=bjensen,ou=People,o=test")
+ .addAttribute("objectClass","top","person")
+ .addAttribute("cn","bjensen")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.add(add);
+ assertThat(result.isSuccess()).isTrue();
+
+ ModifyDNRequest mdn=Requests.newModifyDNRequest("sn=bjensen,ou=People,o=test","sn=bjensen2")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.modifyDN(mdn);
+ assertThat(result.isSuccess()).isTrue();
+
+ ModifyRequest edit= Requests.newModifyRequest("sn=bjensen2,ou=People,o=test")
+ .addModification(ModificationType.REPLACE,"cn","bjensen2")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.modify(edit);
+ assertThat(result.isSuccess()).isTrue();
+
+ DeleteRequest delete=Requests.newDeleteRequest("sn=bjensen2,ou=People,o=test")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.delete(delete);
+ assertThat(result.isSuccess()).isTrue();
+
+ assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People)");
+ }
+ });
+
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(transactionID));
+ assertThat(resEnd.isSuccess()).isTrue();
+ assertThat(resEnd.getOID()).isEqualTo("1.3.6.1.1.21.3");
+
+ //check commit successfully
+ assertThat(connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People)")).isNotNull();
+
+ //check transaction finished
+ String finalTransactionID = transactionID;
+ assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID).setCommit(true));
+ }
+ });
+
+ //rollback by EndTransactionExtendedRequest
+ resStart=connection.extendedRequest(new StartTransactionExtendedRequest());
+ assertThat(resStart.isSuccess()).isTrue();
+ assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1");
+ transactionID=resStart.getTransactionID();
+ assertThat(transactionID).isNotEmpty();
+
+ add=Requests
+ .newAddRequest("ou=People2,o=test")
+ .addAttribute("objectClass", "top", "organizationalUnit")
+ .addAttribute("ou", "People2")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.add(add);
+ assertThat(result.isSuccess()).isTrue();
+
+ resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(transactionID).setCommit(false));
+ assertThat(resEnd.isSuccess()).isTrue();
+ assertThat(resEnd.getOID()).isEqualTo("1.3.6.1.1.21.3");
+
+ //check transaction finished
+ String finalTransactionID1 = transactionID;
+ assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID1).setCommit(false));
+ }
+ });
+
+ //check rollback successfully
+ assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ connection.searchSingleEntry("o=test",SearchScope.SINGLE_LEVEL,"(ou=People2)");
+ }
+ });
+
+ //rollback by error
+ resStart=connection.extendedRequest(new StartTransactionExtendedRequest());
+ assertThat(resStart.isSuccess()).isTrue();
+ assertThat(resStart.getOID()).isEqualTo("1.3.6.1.1.21.1");
+ transactionID=resStart.getTransactionID();
+ assertThat(transactionID).isNotEmpty();
+
+ add= Requests.newAddRequest("sn=bjensen0,ou=People,o=test")
+ .addAttribute("objectClass","top","person")
+ .addAttribute("cn","bjensen0")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.add(add);
+ assertThat(result.isSuccess()).isTrue();
+
+ add= Requests.newAddRequest("sn=bjensen,ou=People,o=test")
+ .addAttribute("objectClass","top","person")
+ .addAttribute("cn","bjensen")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.add(add);
+ assertThat(result.isSuccess()).isTrue();
+
+ mdn=Requests.newModifyDNRequest("sn=bjensen,ou=People,o=test","sn=bjensen2")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.modifyDN(mdn);
+ assertThat(result.isSuccess()).isTrue();
+
+ edit= Requests.newModifyRequest("sn=bjensen2,ou=People,o=test")
+ .addModification(ModificationType.REPLACE,"cn","bjensen2")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.modify(edit);
+ assertThat(result.isSuccess()).isTrue();
+
+ delete=Requests.newDeleteRequest("sn=bjensen2,ou=People,o=test")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.delete(delete);
+ assertThat(result.isSuccess()).isTrue();
+
+ delete=Requests.newDeleteRequest("sn=bjensen3,ou=People,o=test")
+ .addControl(new TransactionSpecificationRequestControl(transactionID))
+ ;
+ result = connection.delete(delete);
+ assertThat(result.isSuccess()).isTrue();
+
+ String finalTransactionID3 = transactionID;
+ assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID3).setCommit(true));
+ }
+ });
+
+ //check transaction finished
+ String finalTransactionID2 = transactionID;
+ assertThrows(CancelledResultException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ EndTransactionExtendedResult resEnd=connection.extendedRequest(new EndTransactionExtendedRequest().setTransactionID(finalTransactionID2).setCommit(false));
+ }
+ });
+
+ //check rollback successfully
+ assertThrows(EntryNotFoundException.class, new Assert.ThrowingRunnable() {
+ @Override
+ public void run() throws Throwable {
+ connection.searchSingleEntry("ou=People,o=test",SearchScope.SINGLE_LEVEL,"(cn=bjensen0)");
+ }
+ });
+
+ }
+}
diff --git a/opendj-server-msad-plugin/pom.xml b/opendj-server-msad-plugin/pom.xml
index 886fb18..54b5706 100644
--- a/opendj-server-msad-plugin/pom.xml
+++ b/opendj-server-msad-plugin/pom.xml
@@ -5,7 +5,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-server-msad-plugin</artifactId>
diff --git a/opendj-server/pom.xml b/opendj-server/pom.xml
index d6c618a..6cb37e6 100644
--- a/opendj-server/pom.xml
+++ b/opendj-server/pom.xml
@@ -19,7 +19,7 @@
<parent>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
</parent>
<artifactId>opendj-server</artifactId>
<name>OpenDJ Server NG</name>
@@ -129,10 +129,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- </plugin>
</plugins>
</build>
<reporting>
diff --git a/pom.xml b/pom.xml
index 580cd21..5ff4e73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,13 +13,13 @@
information: "Portions Copyright [year] [name of copyright owner]".
Copyright 2011-2016 ForgeRock AS.
- Portions Copyright 2017-2024 3A Systems, LLC.
+ Portions Copyright 2017-2025 3A Systems, LLC.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-parent</artifactId>
- <version>4.9.5-SNAPSHOT</version>
+ <version>4.10.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>OpenDJ Directory Services Project</name>
@@ -35,10 +35,9 @@
<product.locales>ca_ES,es,de,fr,ja,ko,pl,zh_CN,zh_TW</product.locales>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<localized.jars.classifier>i18n</localized.jars.classifier>
- <commons.version>2.2.5-SNAPSHOT</commons.version>
- <freemarker.version>2.3.31</freemarker.version>
- <grizzly-framework.version>2.3.35</grizzly-framework.version>
- <metrics-core.version>3.1.2</metrics-core.version>
+ <commons.version>2.4.0</commons.version>
+ <freemarker.version>2.3.34</freemarker.version>
+ <metrics-core.version>4.2.30</metrics-core.version>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<!-- OSGi bundles properties -->
@@ -119,36 +118,28 @@
<tag>HEAD</tag>
</scm>
<repositories>
- <repository>
- <id>ossrh-snapshots</id>
- <name>Sonatype OSS Repository</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
- <snapshots><enabled>true</enabled></snapshots>
- </repository>
- </repositories>
+ <repository>
+ <name>Central Portal Snapshots</name>
+ <id>central-portal-snapshots</id>
+ <url>https://central.sonatype.com/repository/maven-snapshots/</url>
+ <releases>
+ <enabled>false</enabled>
+ </releases>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
<pluginRepositories>
<pluginRepository>
- <id>ossrh-snapshots</id>
- <name>Sonatype OSS Repository</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+ <name>Central Portal Snapshots</name>
+ <id>central-portal-snapshots</id>
+ <url>https://central.sonatype.com/repository/maven-snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
- <distributionManagement>
- <repository>
- <id>ossrh</id>
- <name>Sonatype OSS Repository</name>
- <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
- </repository>
- <snapshotRepository>
- <id>ossrh</id>
- <name>Sonatype OSS Repository</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots</url>
- </snapshotRepository>
- </distributionManagement>
-
<dependencyManagement>
<dependencies>
<dependency>
@@ -315,26 +306,16 @@
</configuration>
</plugin>
<plugin>
- <groupId>org.sonatype.plugins</groupId>
- <artifactId>nexus-staging-maven-plugin</artifactId>
- <version>1.6.13</version>
- <extensions>true</extensions>
- <configuration>
- <serverId>ossrh</serverId>
- <nexusUrl>https://oss.sonatype.org/</nexusUrl>
- <autoReleaseAfterClose>false</autoReleaseAfterClose>
- <stagingProgressTimeoutMinutes>15</stagingProgressTimeoutMinutes>
- <keepStagingRepositoryOnFailure>true</keepStagingRepositoryOnFailure>
- <keepStagingRepositoryOnCloseRuleFailure>true</keepStagingRepositoryOnCloseRuleFailure>
- </configuration>
- <dependencies>
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>15.0</version>
- </dependency>
- </dependencies>
- </plugin>
+ <groupId>org.sonatype.central</groupId>
+ <artifactId>central-publishing-maven-plugin</artifactId>
+ <version>0.8.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <publishingServerId>ossrh</publishingServerId>
+ <autoPublish>true</autoPublish>
+ <waitMaxTime>5400</waitMaxTime>
+ </configuration>
+ </plugin>
</plugins>
<pluginManagement>
@@ -348,7 +329,7 @@
<inherited>true</inherited>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.13.0</version>
+ <version>3.14.0</version>
<configuration>
<fork>true</fork>
<compilerArgs>
@@ -364,83 +345,69 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
- <version>3.6.0</version>
+ <version>3.7.1</version>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
- <version>3.0.0-M7</version>
+ <version>3.1.1</version>
<configuration>
<signTag>true</signTag>
<tagNameFormat>@{project.version}</tagNameFormat>
<allowTimestampedSnapshots>false</allowTimestampedSnapshots>
</configuration>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
- <version>3.3.0</version>
+ <version>3.4.2</version>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
- <version>3.2.3</version>
+ <version>3.4.0</version>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
- <version>3.2.1</version>
+ <version>3.6.0</version>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
- <version>3.2.5</version>
+ <version>3.5.3</version>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
- <version>3.1.0</version>
+ <version>3.4.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
- <version>3.6.0</version>
+ <version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
- <version>3.10.1</version>
+ <version>3.11.2</version>
</plugin>
-
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <version>3.2.0</version>
- <executions>
- <execution>
- <goals>
- <goal>jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
-
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
- <version>3.2.5</version> <!-- M6 DecodeException but got java.nio.BufferOverflowException -->
+ <version>3.5.3</version> <!-- M6 DecodeException but got java.nio.BufferOverflowException -->
<configuration>
<properties>
<property>
@@ -464,7 +431,7 @@
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
- <version>5.1.1</version>
+ <version>5.1.9</version>
<extensions>true</extensions>
<configuration>
<instructions>
@@ -488,6 +455,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
+ <version>3.21.0</version>
<configuration>
<locales>en</locales>
</configuration>
@@ -514,7 +482,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
- <version>3.6.0</version>
+ <version>3.15.1</version>
<configuration>
<skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
</configuration>
@@ -611,7 +579,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
- <version>3.3.0</version>
+ <version>3.6.0</version>
<executions>
<!-- Parse version to generate properties (major.version, minor.version, ...) -->
<execution>
@@ -627,7 +595,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
- <version>1.4</version>
+ <version>3.2.1</version>
<executions>
<execution>
<id>generate-buildnumber</id>
@@ -735,7 +703,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
- <version>1.6</version>
+ <version>3.2.7</version>
<configuration>
<passphrase>${gpg.passphrase}</passphrase>
<useAgent>true</useAgent>
@@ -783,7 +751,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
- <version>2.4</version>
+ <version>3.9.0</version>
<reportSets>
<reportSet>
<reports>
--
Gitblit v1.10.0