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