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