001    /*
002     * Copyright 2007-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.ldap.sdk.Control;
026    import com.unboundid.ldap.sdk.ExtendedRequest;
027    import com.unboundid.ldap.sdk.ExtendedResult;
028    import com.unboundid.ldap.sdk.LDAPConnection;
029    import com.unboundid.ldap.sdk.LDAPException;
030    import com.unboundid.ldap.sdk.ResultCode;
031    import com.unboundid.ldap.sdk.controls.SubtreeDeleteRequestControl;
032    import com.unboundid.ldap.sdk.unboundidds.controls.AccountUsableRequestControl;
033    import com.unboundid.ldap.sdk.unboundidds.controls.
034                BatchedTransactionSpecificationRequestControl;
035    import com.unboundid.ldap.sdk.unboundidds.controls.
036                IntermediateClientRequestControl;
037    import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl;
038    import com.unboundid.util.NotMutable;
039    import com.unboundid.util.ThreadSafety;
040    import com.unboundid.util.ThreadSafetyLevel;
041    
042    import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
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     * This class provides an implementation of the start batched transaction
054     * extended request.  It may be used to begin a transaction that allows multiple
055     * write operations to be processed as a single atomic unit.  The
056     * {@link StartBatchedTransactionExtendedResult} that is returned will include a
057     * a transaction ID.  For each operation that is performed as part of the
058     * transaction, this transaction ID should be included in the corresponding
059     * request through the {@link BatchedTransactionSpecificationRequestControl}.
060     * Finally, after all requests for the transaction have been submitted to the
061     * server, the {@link EndBatchedTransactionExtendedRequest} should be used to
062     * commit that transaction, or it may also be used to abort the transaction if
063     * it is decided that it is no longer needed.
064     * <BR><BR>
065     * Transactions processed using this mechanism are called "batched transactions"
066     * because the associated requests are collected in the server and are only
067     * processed once the {@link EndBatchedTransactionExtendedRequest} has been
068     * received to indicate that the transaction should be committed.  As a result,
069     * it is only possible to include write operations (in particular, add, delete,
070     * modify, modify DN, and password modify operations) in a batched transaction.
071     * Read operations (like search, bind, and compare) cannot be included in a
072     * batched transaction.  However, it is possible to use some controls within the
073     * transaction and they may prove to be sufficient in many cases.  The controls
074     * that can be included in operations that are part of a batched transaction
075     * include:
076     * <UL>
077     *   <LI>{@link AccountUsableRequestControl}</LI>
078     *   <LI>{@link com.unboundid.ldap.sdk.controls.AssertionRequestControl}</LI>
079     *   <LI>{@link IntermediateClientRequestControl}</LI>
080     *   <LI>{@link com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl}</LI>
081     *   <LI>{@link PasswordPolicyRequestControl}</LI>
082     *   <LI>{@link com.unboundid.ldap.sdk.controls.PostReadRequestControl}</LI>
083     *   <LI>{@link com.unboundid.ldap.sdk.controls.PreReadRequestControl}</LI>
084     *   <LI>{@link SubtreeDeleteRequestControl}</LI>
085     * </UL>
086     * In particular, the assertion control may be used to ensure that an operation
087     * is only performed if the target entry matches a given filter (which allows
088     * for an atomic compare-and-swap operation), and the pre-read and post-read
089     * controls may be used to retrieve a copy of an entry immediately before or
090     * immediately after the operation was performed.
091     * <BR><BR>
092     * Note that even though the operations which are part of this transaction
093     * aren't actually processed until the end batched transaction request is
094     * received, the directory server will send back a response for each operation
095     * that is to be performed as part of the transaction.  If the result of this
096     * response is {@link ResultCode#SUCCESS}, then it means that the server has
097     * accepted the operation and it will be processed when the end batched
098     * transaction request is received indicating that the transaction should be
099     * committed.  However, if it has some other result then it indicates that the
100     * request may have been malformed or did not meet the requirements for the
101     * transaction (e.g., it included a control that is not allowed for a
102     * transaction).  Note that even if the server returns a non-success response
103     * for an operation prior to the end batched transaction request, the
104     * transaction will still be active in the server and other operations may still
105     * be included in the transaction if desired.  If it is no longer desirable to
106     * process the transaction, then the end batched transaction request should be
107     * used to abort the transaction.
108     * <BR><BR>
109     * This implementation of batched transactions is based on the
110     * specification defined in draft-zeilenga-ldap-txn, and in particular revision
111     * 11 of that draft.  However, in order to ensure API stability in both the
112     * UnboundID Directory Server and the LDAP SDK for Java, this implementation
113     * will not be updated to reflect changes in the draft.  In the event that the
114     * draft reaches sufficient maturity level to be published as an RFC, then a
115     * separate implementation will be provided that matches that specification.
116     * <BR><BR>
117     * <H2>Example</H2>
118     * The following example demonstrates the process for using batched
119     * transactions.  It will modify two different entries as a single atomic
120     * unit.
121     * <PRE>
122     * // Use the start transaction extended operation to begin a transaction.
123     * StartBatchedTransactionExtendedResult startTxnResult;
124     * try
125     * {
126     *   startTxnResult = (StartBatchedTransactionExtendedResult)
127     *        connection.processExtendedOperation(
128     *             new StartBatchedTransactionExtendedRequest());
129     *   // This doesn't necessarily mean that the operation was successful, since
130     *   // some kinds of extended operations return non-success results under
131     *   // normal conditions.
132     * }
133     * catch (LDAPException le)
134     * {
135     *   // For an extended operation, this generally means that a problem was
136     *   // encountered while trying to send the request or read the result.
137     *   startTxnResult = new StartBatchedTransactionExtendedResult(
138     *        new ExtendedResult(le));
139     * }
140     * LDAPTestUtils.assertResultCodeEquals(startTxnResult, ResultCode.SUCCESS);
141     * ASN1OctetString txnID = startTxnResult.getTransactionID();
142     *
143     *
144     * // At this point, we have a transaction available for use.  If any problem
145     * // arises, we want to ensure that the transaction is aborted, so create a
146     * // try block to process the operations and a finally block to commit or
147     * // abort the transaction.
148     * boolean commit = false;
149     * try
150     * {
151     *   // Create and process a modify operation to update a first entry as part
152     *   // of the transaction.  Make sure to include the transaction specification
153     *   // control in the request to indicate that it should be part of the
154     *   // transaction.
155     *   ModifyRequest firstModifyRequest = new ModifyRequest(
156     *        "cn=first,dc=example,dc=com",
157     *        new Modification(ModificationType.REPLACE, "description", "first"));
158     *   firstModifyRequest.addControl(
159     *        new BatchedTransactionSpecificationRequestControl(txnID));
160     *   LDAPResult firstModifyResult;
161     *   try
162     *   {
163     *     firstModifyResult = connection.modify(firstModifyRequest);
164     *   }
165     *   catch (LDAPException le)
166     *   {
167     *     firstModifyResult = le.toLDAPResult();
168     *   }
169     *   LDAPTestUtils.assertResultCodeEquals(firstModifyResult,
170     *        ResultCode.SUCCESS);
171     *
172     *   // Perform a second modify operation as part of the transaction.
173     *   ModifyRequest secondModifyRequest = new ModifyRequest(
174     *        "cn=second,dc=example,dc=com",
175     *        new Modification(ModificationType.REPLACE, "description", "second"));
176     *   secondModifyRequest.addControl(
177     *        new BatchedTransactionSpecificationRequestControl(txnID));
178     *   LDAPResult secondModifyResult;
179     *   try
180     *   {
181     *     secondModifyResult = connection.modify(secondModifyRequest);
182     *   }
183     *   catch (LDAPException le)
184     *   {
185     *     secondModifyResult = le.toLDAPResult();
186     *   }
187     *   LDAPTestUtils.assertResultCodeEquals(secondModifyResult,
188     *        ResultCode.SUCCESS);
189     *
190     *   // If we've gotten here, then all writes have been processed successfully
191     *   // and we can indicate that the transaction should be committed rather
192     *   // than aborted.
193     *   commit = true;
194     * }
195     * finally
196     * {
197     *   // Commit or abort the transaction.
198     *   EndBatchedTransactionExtendedResult endTxnResult;
199     *   try
200     *   {
201     *     endTxnResult = (EndBatchedTransactionExtendedResult)
202     *          connection.processExtendedOperation(
203     *               new EndBatchedTransactionExtendedRequest(txnID, commit));
204     *   }
205     *   catch (LDAPException le)
206     *   {
207     *     endTxnResult = new EndBatchedTransactionExtendedResult(
208     *          new ExtendedResult(le));
209     *   }
210     *   LDAPTestUtils.assertResultCodeEquals(endTxnResult, ResultCode.SUCCESS);
211     * }
212     * </PRE>
213     */
214    @NotMutable()
215    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
216    public final class StartBatchedTransactionExtendedRequest
217           extends ExtendedRequest
218    {
219      /**
220       * The OID (1.3.6.1.4.1.30221.2.6.1) for the start batched transaction
221       * extended request.
222       */
223      public static final String START_BATCHED_TRANSACTION_REQUEST_OID =
224           "1.3.6.1.4.1.30221.2.6.1";
225    
226    
227    
228      /**
229       * The serial version UID for this serializable class.
230       */
231      private static final long serialVersionUID = 7141543268276702748L;
232    
233    
234    
235      // This is an ugly hack to prevent checkstyle from complaining about imports
236      // for classes that are needed by javadoc @link elements but aren't otherwise
237      // used in the class.  It appears that checkstyle does not recognize the use
238      // of these classes in javadoc @link elements so we must ensure that they are
239      // referenced elsewhere in the class to prevent checkstyle from complaining.
240      static
241      {
242        final AccountUsableRequestControl                   c1 = null;
243        final BatchedTransactionSpecificationRequestControl c2 = null;
244        final IntermediateClientRequestControl              c3 = null;
245        final PasswordPolicyRequestControl                  c4 = null;
246        final SubtreeDeleteRequestControl                   c5 = null;
247      }
248    
249    
250    
251      /**
252       * Creates a new start batched transaction extended request.
253       */
254      public StartBatchedTransactionExtendedRequest()
255      {
256        super(START_BATCHED_TRANSACTION_REQUEST_OID);
257      }
258    
259    
260    
261      /**
262       * Creates a new start batched transaction extended request.
263       *
264       * @param  controls  The set of controls to include in the request.
265       */
266      public StartBatchedTransactionExtendedRequest(final Control[] controls)
267      {
268        super(START_BATCHED_TRANSACTION_REQUEST_OID, controls);
269      }
270    
271    
272    
273      /**
274       * Creates a new start batched transaction extended request from the provided
275       * generic extended request.
276       *
277       * @param  extendedRequest  The generic extended request to use to create this
278       *                          start batched transaction extended request.
279       *
280       * @throws  LDAPException  If a problem occurs while decoding the request.
281       */
282      public StartBatchedTransactionExtendedRequest(
283                  final ExtendedRequest extendedRequest)
284             throws LDAPException
285      {
286        super(extendedRequest);
287    
288        if (extendedRequest.hasValue())
289        {
290          throw new LDAPException(ResultCode.DECODING_ERROR,
291                                  ERR_START_TXN_REQUEST_HAS_VALUE.get());
292        }
293      }
294    
295    
296    
297      /**
298       * {@inheritDoc}
299       */
300      @Override()
301      public StartBatchedTransactionExtendedResult process(
302                  final LDAPConnection connection, final int depth)
303             throws LDAPException
304      {
305        final ExtendedResult extendedResponse = super.process(connection, depth);
306        return new StartBatchedTransactionExtendedResult(extendedResponse);
307      }
308    
309    
310    
311      /**
312       * {@inheritDoc}
313       */
314      @Override()
315      public StartBatchedTransactionExtendedRequest duplicate()
316      {
317        return duplicate(getControls());
318      }
319    
320    
321    
322      /**
323       * {@inheritDoc}
324       */
325      @Override()
326      public StartBatchedTransactionExtendedRequest duplicate(
327                  final Control[] controls)
328      {
329        final StartBatchedTransactionExtendedRequest r =
330             new StartBatchedTransactionExtendedRequest(controls);
331        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
332        return r;
333      }
334    
335    
336    
337      /**
338       * {@inheritDoc}
339       */
340      @Override()
341      public String getExtendedRequestName()
342      {
343        return INFO_EXTENDED_REQUEST_NAME_START_BATCHED_TXN.get();
344      }
345    
346    
347    
348      /**
349       * {@inheritDoc}
350       */
351      @Override()
352      public void toString(final StringBuilder buffer)
353      {
354        buffer.append("StartBatchedTransactionExtendedRequest(");
355    
356        final Control[] controls = getControls();
357        if (controls.length > 0)
358        {
359          buffer.append("controls={");
360          for (int i=0; i < controls.length; i++)
361          {
362            if (i > 0)
363            {
364              buffer.append(", ");
365            }
366    
367            buffer.append(controls[i]);
368          }
369          buffer.append('}');
370        }
371    
372        buffer.append(')');
373      }
374    }