001/*
002 * Copyright 2008-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-2020 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) 2008-2020 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.asn1.ASN1Element;
041import com.unboundid.asn1.ASN1OctetString;
042import com.unboundid.asn1.ASN1Sequence;
043import com.unboundid.ldap.sdk.Control;
044import com.unboundid.ldap.sdk.ExtendedRequest;
045import com.unboundid.ldap.sdk.ExtendedResult;
046import com.unboundid.ldap.sdk.LDAPConnection;
047import com.unboundid.ldap.sdk.LDAPException;
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.ldap.sdk.unboundidds.controls.
050            InteractiveTransactionSpecificationRequestControl;
051import com.unboundid.util.Debug;
052import com.unboundid.util.NotMutable;
053import com.unboundid.util.NotNull;
054import com.unboundid.util.Nullable;
055import com.unboundid.util.StaticUtils;
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 * <BLOCKQUOTE>
065 *   <B>NOTE:</B>  The use of interactive transactions is discouraged because it
066 *   can create conditions which are prone to deadlocks between operations that
067 *   may result in the cancellation of one or both operations.  It is strongly
068 *   recommended that standard LDAP transactions (which may be started using a
069 *   {@link com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest})
070 *   or a multi-update extended operation be used instead.  Although they cannot
071 *   include arbitrary read operations, LDAP transactions and multi-update
072 *   operations may be used in conjunction with the
073 *   {@link com.unboundid.ldap.sdk.controls.AssertionRequestControl},
074 *   {@link com.unboundid.ldap.sdk.controls.PreReadRequestControl}, and
075 *   {@link com.unboundid.ldap.sdk.controls.PostReadRequestControl} to
076 *   incorporate some read capability into a transaction, and in conjunction
077 *   with the {@link com.unboundid.ldap.sdk.ModificationType#INCREMENT}
078 *   modification type to increment integer values without the need to know the
079 *   precise value before or after the operation (although the pre-read and/or
080 *   post-read controls may be used to determine that).
081 * </BLOCKQUOTE>
082 * This class provides an implementation of the start interactive transaction
083 * extended request.  It may be used to begin a transaction that allows multiple
084 * operations to be processed as a single atomic unit.  Interactive transactions
085 * may include read operations, in which case it is guaranteed that no
086 * operations outside of the transaction will be allowed to access the
087 * associated entries until the transaction has been committed or aborted.  The
088 * {@link StartInteractiveTransactionExtendedResult} that is returned will
089 * include a a transaction ID, which should be included in each operation that
090 * is part of the transaction using the
091 * {@link InteractiveTransactionSpecificationRequestControl}.  After all
092 * requests for the transaction have been submitted to the server, the
093 * {@link EndInteractiveTransactionExtendedRequest} should be used to
094 * commit that transaction, or it may also be used to abort the transaction if
095 * it is decided that it is no longer needed.
096 * <BR>
097 * <BLOCKQUOTE>
098 *   <B>NOTE:</B>  This class, and other classes within the
099 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
100 *   supported for use against Ping Identity, UnboundID, and
101 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
102 *   for proprietary functionality or for external specifications that are not
103 *   considered stable or mature enough to be guaranteed to work in an
104 *   interoperable way with other types of LDAP servers.
105 * </BLOCKQUOTE>
106 * <BR>
107 * The start transaction extended request may include an element which indicates
108 * the base DN below which all operations will be attempted.  This may be used
109 * to allow the Directory Server to tailor the transaction to the appropriate
110 * backend.
111 * <BR><BR>
112 * Whenever the client sends a start interactive transaction request to the
113 * server, the {@link StartInteractiveTransactionExtendedResult} that is
114 * returned will include a transaction ID that may be used to identify the
115 * transaction for all operations which are to be performed as part of the
116 * transaction.  This transaction ID should be included in a
117 * {@link InteractiveTransactionSpecificationRequestControl} attached to each
118 * request that is to be processed as part of the transaction.  When the
119 * transaction has completed, the
120 * {@link EndInteractiveTransactionExtendedRequest} may be used to commit it,
121 * and it may also be used at any time to abort the transaction if it is no
122 * longer needed.
123 * <H2>Example</H2>
124 * The following example demonstrates the process for creating an interactive
125 * transaction, processing multiple requests as part of that transaction, and
126 * then commits the transaction.
127 * <PRE>
128 * // Start the interactive transaction and get the transaction ID.
129 * StartInteractiveTransactionExtendedRequest startTxnRequest =
130 *      new StartInteractiveTransactionExtendedRequest("dc=example,dc=com");
131 * StartInteractiveTransactionExtendedResult startTxnResult =
132 *      (StartInteractiveTransactionExtendedResult)
133 *      connection.processExtendedOperation(startTxnRequest);
134 * if (startTxnResult.getResultCode() != ResultCode.SUCCESS)
135 * {
136 *   throw new LDAPException(startTxnResult);
137 * }
138 * ASN1OctetString txnID = startTxnResult.getTransactionID();
139 *
140 * // At this point, we have a valid transaction.  We want to ensure that the
141 * // transaction is aborted if any failure occurs, so do that in a
142 * // try-finally block.
143 * boolean txnFailed = true;
144 * try
145 * {
146 *   // Perform a search to find all users in the "Sales" department.
147 *   SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
148 *        SearchScope.SUB, Filter.createEqualityFilter("ou", "Sales"));
149 *   searchRequest.addControl(
150 *        new InteractiveTransactionSpecificationRequestControl(txnID, true,
151 *             true));
152 *
153 *   SearchResult searchResult = connection.search(searchRequest);
154 *   if (searchResult.getResultCode() != ResultCode.SUCCESS)
155 *   {
156 *     throw new LDAPException(searchResult);
157 *   }
158 *
159 *   // Iterate through all of the users and assign a new fax number to each
160 *   // of them.
161 *   for (SearchResultEntry e : searchResult.getSearchEntries())
162 *   {
163 *     ModifyRequest modifyRequest = new ModifyRequest(e.getDN(),
164 *          new Modification(ModificationType.REPLACE,
165 *               "facsimileTelephoneNumber", "+1 123 456 7890"));
166 *     modifyRequest.addControl(
167 *          new InteractiveTransactionSpecificationRequestControl(txnID, true,
168 *
169 *               true));
170 *     connection.modify(modifyRequest);
171 *   }
172 *
173 *   // Commit the transaction.
174 *   ExtendedResult endTxnResult = connection.processExtendedOperation(
175 *        new EndInteractiveTransactionExtendedRequest(txnID, true));
176 *   if (endTxnResult.getResultCode() == ResultCode.SUCCESS)
177 *   {
178 *     txnFailed = false;
179 *   }
180 * }
181 * finally
182 * {
183 *   if (txnFailed)
184 *   {
185 *     connection.processExtendedOperation(
186 *          new EndInteractiveTransactionExtendedRequest(txnID, false));
187 *   }
188 * }
189 * </PRE>
190 */
191@NotMutable()
192@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
193public final class StartInteractiveTransactionExtendedRequest
194       extends ExtendedRequest
195{
196  /**
197   * The OID (1.3.6.1.4.1.30221.2.6.3) for the start interactive transaction
198   * extended request.
199   */
200  @NotNull public static final String
201       START_INTERACTIVE_TRANSACTION_REQUEST_OID =
202            "1.3.6.1.4.1.30221.2.6.3";
203
204
205
206  /**
207   * The BER type for the {@code baseDN} element of the request.
208   */
209  private static final byte TYPE_BASE_DN = (byte) 0x80;
210
211
212
213  /**
214   * The serial version UID for this serializable class.
215   */
216  private static final long serialVersionUID = 4475028061132753546L;
217
218
219
220  // The base DN for this request, if specified.
221  @Nullable private final String baseDN;
222
223
224
225  /**
226   * Creates a new start interactive transaction extended request with no base
227   * DN.
228   */
229  public StartInteractiveTransactionExtendedRequest()
230  {
231    super(START_INTERACTIVE_TRANSACTION_REQUEST_OID);
232
233    baseDN = null;
234  }
235
236
237
238  /**
239   * Creates a new start interactive transaction extended request.
240   *
241   * @param  baseDN  The base DN to use for the request.  It may be {@code null}
242   *                 if no base DN should be provided.
243   */
244  public StartInteractiveTransactionExtendedRequest(
245              @Nullable final String baseDN)
246  {
247    super(START_INTERACTIVE_TRANSACTION_REQUEST_OID, encodeValue(baseDN));
248
249    this.baseDN = baseDN;
250  }
251
252
253
254  /**
255   * Creates a new start interactive transaction extended request.
256   *
257   * @param  baseDN    The base DN to use for the request.  It may be
258   *                   {@code null} if no base DN should be provided.
259   * @param  controls  The set of controls to include in the request.
260   */
261  public StartInteractiveTransactionExtendedRequest(
262              @Nullable final String baseDN,
263              @Nullable final Control[] controls)
264  {
265    super(START_INTERACTIVE_TRANSACTION_REQUEST_OID, encodeValue(baseDN),
266          controls);
267
268    this.baseDN = baseDN;
269  }
270
271
272
273  /**
274   * Creates a new start interactive transaction extended request from the
275   * provided generic extended request.
276   *
277   * @param  extendedRequest  The generic extended request to use to create this
278   *                          start interactive transaction extended request.
279   *
280   * @throws  LDAPException  If a problem occurs while decoding the request.
281   */
282  public StartInteractiveTransactionExtendedRequest(
283              @NotNull final ExtendedRequest extendedRequest)
284         throws LDAPException
285  {
286    super(extendedRequest);
287
288    if (! extendedRequest.hasValue())
289    {
290      baseDN = null;
291      return;
292    }
293
294    String baseDNStr = null;
295    try
296    {
297      final ASN1Element valueElement =
298           ASN1Element.decode(extendedRequest.getValue().getValue());
299      final ASN1Sequence valueSequence =
300           ASN1Sequence.decodeAsSequence(valueElement);
301      for (final ASN1Element e : valueSequence.elements())
302      {
303        if (e.getType() == TYPE_BASE_DN)
304        {
305          baseDNStr = ASN1OctetString.decodeAsOctetString(e).stringValue();
306        }
307        else
308        {
309          throw new LDAPException(ResultCode.DECODING_ERROR,
310               ERR_START_INT_TXN_REQUEST_INVALID_ELEMENT.get(
311                    StaticUtils.toHex(e.getType())));
312        }
313      }
314    }
315    catch (final LDAPException le)
316    {
317      Debug.debugException(le);
318      throw le;
319    }
320    catch (final Exception e)
321    {
322      Debug.debugException(e);
323      throw new LDAPException(ResultCode.DECODING_ERROR,
324           ERR_START_INT_TXN_REQUEST_VALUE_NOT_SEQUENCE.get(e.getMessage()), e);
325    }
326
327    baseDN = baseDNStr;
328  }
329
330
331
332  /**
333   * Encodes the provided information into an ASN.1 octet string suitable for
334   * use as the value of this extended request.
335   *
336   * @param  baseDN  The base DN to use for the request.  It may be {@code null}
337   *                 if no base DN should be provided.
338   *
339   * @return  The ASN.1 octet string containing the encoded value, or
340   *          {@code null} if no value should be used.
341   */
342  @Nullable()
343  private static ASN1OctetString encodeValue(@Nullable final String baseDN)
344  {
345    if (baseDN == null)
346    {
347      return null;
348    }
349
350    final ASN1Element[] elements =
351    {
352      new ASN1OctetString(TYPE_BASE_DN, baseDN)
353    };
354
355    return new ASN1OctetString(new ASN1Sequence(elements).encode());
356  }
357
358
359
360  /**
361   * Retrieves the base DN for this start interactive transaction extended
362   * request, if available.
363   *
364   * @return  The base DN for this start interactive transaction extended
365   *          request, or {@code null} if none was provided.
366   */
367  @Nullable()
368  public String getBaseDN()
369  {
370    return baseDN;
371  }
372
373
374
375  /**
376   * {@inheritDoc}
377   */
378  @Override()
379  @NotNull()
380  public StartInteractiveTransactionExtendedResult process(
381              @NotNull final LDAPConnection connection, final int depth)
382         throws LDAPException
383  {
384    final ExtendedResult extendedResponse = super.process(connection, depth);
385    return new StartInteractiveTransactionExtendedResult(extendedResponse);
386  }
387
388
389
390  /**
391   * {@inheritDoc}
392   */
393  @Override()
394  @NotNull()
395  public StartInteractiveTransactionExtendedRequest duplicate()
396  {
397    return duplicate(getControls());
398  }
399
400
401
402  /**
403   * {@inheritDoc}
404   */
405  @Override()
406  @NotNull()
407  public StartInteractiveTransactionExtendedRequest duplicate(
408              @Nullable final Control[] controls)
409  {
410    final StartInteractiveTransactionExtendedRequest r =
411         new StartInteractiveTransactionExtendedRequest(baseDN, controls);
412    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
413    return r;
414  }
415
416
417
418  /**
419   * {@inheritDoc}
420   */
421  @Override()
422  @NotNull()
423  public String getExtendedRequestName()
424  {
425    return INFO_EXTENDED_REQUEST_NAME_START_INTERACTIVE_TXN.get();
426  }
427
428
429
430  /**
431   * {@inheritDoc}
432   */
433  @Override()
434  public void toString(@NotNull final StringBuilder buffer)
435  {
436    buffer.append("StartInteractiveTransactionExtendedRequest(");
437
438    if (baseDN != null)
439    {
440      buffer.append("baseDN='");
441      buffer.append(baseDN);
442      buffer.append('\'');
443    }
444
445    final Control[] controls = getControls();
446    if (controls.length > 0)
447    {
448      if (baseDN != null)
449      {
450        buffer.append(", ");
451      }
452      buffer.append("controls={");
453      for (int i=0; i < controls.length; i++)
454      {
455        if (i > 0)
456        {
457          buffer.append(", ");
458        }
459
460        buffer.append(controls[i]);
461      }
462      buffer.append('}');
463    }
464
465    buffer.append(')');
466  }
467}