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