001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2007-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.extensions;
037
038
039
040import com.unboundid.ldap.sdk.Control;
041import com.unboundid.ldap.sdk.ExtendedRequest;
042import com.unboundid.ldap.sdk.ExtendedResult;
043import com.unboundid.ldap.sdk.LDAPConnection;
044import com.unboundid.ldap.sdk.LDAPException;
045import com.unboundid.ldap.sdk.ResultCode;
046import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
047import com.unboundid.ldap.sdk.unboundidds.controls.AccountUsableRequestControl;
048import com.unboundid.ldap.sdk.unboundidds.controls.
049            BatchedTransactionSpecificationRequestControl;
050import com.unboundid.ldap.sdk.unboundidds.controls.
051            IntermediateClientRequestControl;
052import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl;
053import com.unboundid.util.NotMutable;
054import com.unboundid.util.NotNull;
055import com.unboundid.util.Nullable;
056import com.unboundid.util.ThreadSafety;
057import com.unboundid.util.ThreadSafetyLevel;
058
059import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
060
061
062
063/**
064 * This class provides an implementation of the start batched transaction
065 * extended request.  It may be used to begin a transaction that allows multiple
066 * write operations to be processed as a single atomic unit.  The
067 * {@link StartBatchedTransactionExtendedResult} that is returned will include a
068 * a transaction ID.  For each operation that is performed as part of the
069 * transaction, this transaction ID should be included in the corresponding
070 * request through the {@link BatchedTransactionSpecificationRequestControl}.
071 * Finally, after all requests for the transaction have been submitted to the
072 * server, the {@link EndBatchedTransactionExtendedRequest} should be used to
073 * commit that transaction, or it may also be used to abort the transaction if
074 * it is decided that it is no longer needed.
075 * <BR>
076 * <BLOCKQUOTE>
077 *   <B>NOTE:</B>  This class, and other classes within the
078 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
079 *   supported for use against Ping Identity, UnboundID, and
080 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
081 *   for proprietary functionality or for external specifications that are not
082 *   considered stable or mature enough to be guaranteed to work in an
083 *   interoperable way with other types of LDAP servers.
084 * </BLOCKQUOTE>
085 * <BR>
086 * Transactions processed using this mechanism are called "batched transactions"
087 * because the associated requests are collected in the server and are only
088 * processed once the {@link EndBatchedTransactionExtendedRequest} has been
089 * received to indicate that the transaction should be committed.  As a result,
090 * it is only possible to include write operations (in particular, add, delete,
091 * modify, modify DN, and password modify operations) in a batched transaction.
092 * Read operations (like search, bind, and compare) cannot be included in a
093 * batched transaction.  However, it is possible to use some controls within the
094 * transaction and they may prove to be sufficient in many cases.  The controls
095 * that can be included in operations that are part of a batched transaction
096 * include:
097 * <UL>
098 *   <LI>{@link AccountUsableRequestControl}</LI>
099 *   <LI>{@link com.unboundid.ldap.sdk.controls.AssertionRequestControl}</LI>
100 *   <LI>{@link IntermediateClientRequestControl}</LI>
101 *   <LI>{@link com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl}</LI>
102 *   <LI>{@link PasswordPolicyRequestControl}</LI>
103 *   <LI>{@link com.unboundid.ldap.sdk.controls.PostReadRequestControl}</LI>
104 *   <LI>{@link com.unboundid.ldap.sdk.controls.PreReadRequestControl}</LI>
105 *   <LI>{@link SubtreeDeleteRequestControl}</LI>
106 * </UL>
107 * In particular, the assertion control may be used to ensure that an operation
108 * is only performed if the target entry matches a given filter (which allows
109 * for an atomic compare-and-swap operation), and the pre-read and post-read
110 * controls may be used to retrieve a copy of an entry immediately before or
111 * immediately after the operation was performed.
112 * <BR><BR>
113 * Note that even though the operations which are part of this transaction
114 * aren't actually processed until the end batched transaction request is
115 * received, the directory server will send back a response for each operation
116 * that is to be performed as part of the transaction.  If the result of this
117 * response is {@link ResultCode#SUCCESS}, then it means that the server has
118 * accepted the operation and it will be processed when the end batched
119 * transaction request is received indicating that the transaction should be
120 * committed.  However, if it has some other result then it indicates that the
121 * request may have been malformed or did not meet the requirements for the
122 * transaction (e.g., it included a control that is not allowed for a
123 * transaction).  Note that even if the server returns a non-success response
124 * for an operation prior to the end batched transaction request, the
125 * transaction will still be active in the server and other operations may still
126 * be included in the transaction if desired.  If it is no longer desirable to
127 * process the transaction, then the end batched transaction request should be
128 * used to abort the transaction.
129 * <BR><BR>
130 * <H2>Example</H2>
131 * The following example demonstrates the process for using batched
132 * transactions.  It will modify two different entries as a single atomic
133 * unit.
134 * <PRE>
135 * // Use the start transaction extended operation to begin a transaction.
136 * StartBatchedTransactionExtendedResult startTxnResult;
137 * try
138 * {
139 *   startTxnResult = (StartBatchedTransactionExtendedResult)
140 *        connection.processExtendedOperation(
141 *             new StartBatchedTransactionExtendedRequest());
142 *   // This doesn't necessarily mean that the operation was successful, since
143 *   // some kinds of extended operations return non-success results under
144 *   // normal conditions.
145 * }
146 * catch (LDAPException le)
147 * {
148 *   // For an extended operation, this generally means that a problem was
149 *   // encountered while trying to send the request or read the result.
150 *   startTxnResult = new StartBatchedTransactionExtendedResult(
151 *        new ExtendedResult(le));
152 * }
153 * LDAPTestUtils.assertResultCodeEquals(startTxnResult, ResultCode.SUCCESS);
154 * ASN1OctetString txnID = startTxnResult.getTransactionID();
155 *
156 *
157 * // At this point, we have a transaction available for use.  If any problem
158 * // arises, we want to ensure that the transaction is aborted, so create a
159 * // try block to process the operations and a finally block to commit or
160 * // abort the transaction.
161 * boolean commit = false;
162 * try
163 * {
164 *   // Create and process a modify operation to update a first entry as part
165 *   // of the transaction.  Make sure to include the transaction specification
166 *   // control in the request to indicate that it should be part of the
167 *   // transaction.
168 *   ModifyRequest firstModifyRequest = new ModifyRequest(
169 *        "cn=first,dc=example,dc=com",
170 *        new Modification(ModificationType.REPLACE, "description", "first"));
171 *   firstModifyRequest.addControl(
172 *        new BatchedTransactionSpecificationRequestControl(txnID));
173 *   LDAPResult firstModifyResult;
174 *   try
175 *   {
176 *     firstModifyResult = connection.modify(firstModifyRequest);
177 *   }
178 *   catch (LDAPException le)
179 *   {
180 *     firstModifyResult = le.toLDAPResult();
181 *   }
182 *   LDAPTestUtils.assertResultCodeEquals(firstModifyResult,
183 *        ResultCode.SUCCESS);
184 *
185 *   // Perform a second modify operation as part of the transaction.
186 *   ModifyRequest secondModifyRequest = new ModifyRequest(
187 *        "cn=second,dc=example,dc=com",
188 *        new Modification(ModificationType.REPLACE, "description", "second"));
189 *   secondModifyRequest.addControl(
190 *        new BatchedTransactionSpecificationRequestControl(txnID));
191 *   LDAPResult secondModifyResult;
192 *   try
193 *   {
194 *     secondModifyResult = connection.modify(secondModifyRequest);
195 *   }
196 *   catch (LDAPException le)
197 *   {
198 *     secondModifyResult = le.toLDAPResult();
199 *   }
200 *   LDAPTestUtils.assertResultCodeEquals(secondModifyResult,
201 *        ResultCode.SUCCESS);
202 *
203 *   // If we've gotten here, then all writes have been processed successfully
204 *   // and we can indicate that the transaction should be committed rather
205 *   // than aborted.
206 *   commit = true;
207 * }
208 * finally
209 * {
210 *   // Commit or abort the transaction.
211 *   EndBatchedTransactionExtendedResult endTxnResult;
212 *   try
213 *   {
214 *     endTxnResult = (EndBatchedTransactionExtendedResult)
215 *          connection.processExtendedOperation(
216 *               new EndBatchedTransactionExtendedRequest(txnID, commit));
217 *   }
218 *   catch (LDAPException le)
219 *   {
220 *     endTxnResult = new EndBatchedTransactionExtendedResult(
221 *          new ExtendedResult(le));
222 *   }
223 *   LDAPTestUtils.assertResultCodeEquals(endTxnResult, ResultCode.SUCCESS);
224 * }
225 * </PRE>
226 */
227@NotMutable()
228@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
229public final class StartBatchedTransactionExtendedRequest
230       extends ExtendedRequest
231{
232  /**
233   * The OID (1.3.6.1.4.1.30221.2.6.1) for the start batched transaction
234   * extended request.
235   */
236  @NotNull public static final String START_BATCHED_TRANSACTION_REQUEST_OID =
237       "1.3.6.1.4.1.30221.2.6.1";
238
239
240
241  /**
242   * The serial version UID for this serializable class.
243   */
244  private static final long serialVersionUID = 7141543268276702748L;
245
246
247
248  /**
249   * Creates a new start batched transaction extended request.
250   */
251  public StartBatchedTransactionExtendedRequest()
252  {
253    super(START_BATCHED_TRANSACTION_REQUEST_OID);
254  }
255
256
257
258  /**
259   * Creates a new start batched transaction extended request.
260   *
261   * @param  controls  The set of controls to include in the request.
262   */
263  public StartBatchedTransactionExtendedRequest(
264              @Nullable final Control[] controls)
265  {
266    super(START_BATCHED_TRANSACTION_REQUEST_OID, controls);
267  }
268
269
270
271  /**
272   * Creates a new start batched transaction extended request from the provided
273   * generic extended request.
274   *
275   * @param  extendedRequest  The generic extended request to use to create this
276   *                          start batched transaction extended request.
277   *
278   * @throws  LDAPException  If a problem occurs while decoding the request.
279   */
280  public StartBatchedTransactionExtendedRequest(
281              @NotNull final ExtendedRequest extendedRequest)
282         throws LDAPException
283  {
284    super(extendedRequest);
285
286    if (extendedRequest.hasValue())
287    {
288      throw new LDAPException(ResultCode.DECODING_ERROR,
289                              ERR_START_TXN_REQUEST_HAS_VALUE.get());
290    }
291  }
292
293
294
295  /**
296   * {@inheritDoc}
297   */
298  @Override()
299  @NotNull()
300  public StartBatchedTransactionExtendedResult process(
301              @NotNull final LDAPConnection connection, final int depth)
302         throws LDAPException
303  {
304    final ExtendedResult extendedResponse = super.process(connection, depth);
305    return new StartBatchedTransactionExtendedResult(extendedResponse);
306  }
307
308
309
310  /**
311   * {@inheritDoc}
312   */
313  @Override()
314  @NotNull()
315  public StartBatchedTransactionExtendedRequest duplicate()
316  {
317    return duplicate(getControls());
318  }
319
320
321
322  /**
323   * {@inheritDoc}
324   */
325  @Override()
326  @NotNull()
327  public StartBatchedTransactionExtendedRequest duplicate(
328              @Nullable final Control[] controls)
329  {
330    final StartBatchedTransactionExtendedRequest r =
331         new StartBatchedTransactionExtendedRequest(controls);
332    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
333    r.setIntermediateResponseListener(getIntermediateResponseListener());
334    r.setReferralDepth(getReferralDepth());
335    r.setReferralConnector(getReferralConnectorInternal());
336    return r;
337  }
338
339
340
341  /**
342   * {@inheritDoc}
343   */
344  @Override()
345  @NotNull()
346  public String getExtendedRequestName()
347  {
348    return INFO_EXTENDED_REQUEST_NAME_START_BATCHED_TXN.get();
349  }
350
351
352
353  /**
354   * {@inheritDoc}
355   */
356  @Override()
357  public void toString(@NotNull final StringBuilder buffer)
358  {
359    buffer.append("StartBatchedTransactionExtendedRequest(");
360
361    final Control[] controls = getControls();
362    if (controls.length > 0)
363    {
364      buffer.append("controls={");
365      for (int i=0; i < controls.length; i++)
366      {
367        if (i > 0)
368        {
369          buffer.append(", ");
370        }
371
372        buffer.append(controls[i]);
373      }
374      buffer.append('}');
375    }
376
377    buffer.append(')');
378  }
379}