001    /*
002     * Copyright 2010-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-2013 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.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.TransactionSpecificationRequestControl;
032    import com.unboundid.util.NotMutable;
033    import com.unboundid.util.ThreadSafety;
034    import com.unboundid.util.ThreadSafetyLevel;
035    
036    import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*;
037    
038    
039    
040    /**
041     * This class provides an implementation of the start transaction extended
042     * request as defined in
043     * <A HREF="http://www.ietf.org/rfc/rfc5805.txt">RFC 5805</A>.  It may be used
044     * to begin a transaction that allows multiple write operations to be processed
045     * as a single atomic unit.  The {@link StartTransactionExtendedResult} that is
046     * returned will include a transaction ID.  For each operation that is performed
047     * as part of the transaction, this transaction ID should be included in the
048     * corresponding request through the
049     * {@link TransactionSpecificationRequestControl}.  Finally, after all requests
050     * for the transaction have been submitted to the server, the
051     * {@link EndTransactionExtendedRequest} should be used to commit that
052     * transaction, or it may also be used to abort the transaction if it is decided
053     * that it is no longer needed.
054     * <BR><BR>
055     * <H2>Example</H2>
056     * The following example demonstrates the process for using LDAP  transactions.
057     * It will modify two different entries as a single atomic unit.  In each case,
058     * it will use the post-read control to retrieve a copy of the updated entry.
059     * <PRE>
060     *   // Send the start transaction operation and get the transaction ID.
061     *   StartTransactionExtendedRequest startTxnRequest =
062     *        new StartTransactionExtendedRequest();
063     *   StartTransactionExtendedResult startTxnResult =
064     *        (StartTransactionExtendedResult)
065     *        connection.processExtendedOperation(startTxnRequest);
066     *   if (startTxnResult.getResultCode() != ResultCode.SUCCESS)
067     *   {
068     *     throw new LDAPException(startTxnResult);
069     *   }
070     *   ASN1OctetString txnID = startTxnResult.getTransactionID();
071     *
072     *   // At this point, we have a transaction available for use.  If any error
073     *   // occurs, we will want to make sure that the transaction is aborted, so
074     *   // use a try/finally block to handle that.
075     *   boolean shouldAbort = true;
076     *   try
077     *   {
078     *     // Create and send the first modify request as part of the transaction.
079     *     // Make sure to include the transaction specification control and the
080     *     // post-read request control in the modify request.
081     *     ModifyRequest modifyRequest1 = new ModifyRequest(
082     *          "cn=first,dc=example,dc=com",
083     *          new Modification(ModificationType.REPLACE, "description", "first");
084     *     modifyRequest1.addControl(new TransactionSpecificationControl(txnID));
085     *     modifyRequest1.addControl(new PostReadRequestControl());
086     *     LDAPResult modifyResult1 = connection.modify(modifyRequest1);
087     *
088     *     // Create and send the second modify request as part of the transaction.
089     *     // Again, make sure to include the appropriate controls in the request.
090     *     ModifyRequest modifyRequest2 = new ModifyRequest(
091     *          "cn=second,dc=example,dc=com",
092     *          new Modification(ModificationType.REPLACE, "description", "second");
093     *     modifyRequest2.addControl(new TransactionSpecificationControl(txnID));
094     *     modifyRequest2.addControl(new PostReadRequestControl());
095     *     LDAPResult modifyResult2 = connection.modify(modifyRequest1);
096     *
097     *     // Now we're ready to commit, which we can do with the end transaction
098     *     // request with the commit flag set to true.
099     *     EndTransactionExtendedRequest commitRequest =
100     *          new EndTransactionExtendedRequest(txnID, true);
101     *     EndTransactionExtendedResult commitResult =
102     *          (EndTransactionExtendedResult)
103     *          connection.processExtendedOperation(commitRequest);
104     *     if (commitResult.getResultCode() == ResultCode.SUCCESS)
105     *     {
106     *       System.out.println("The transaction was committed successfully.");
107     *
108     *       // Everything was successful, so we don't need to abort anything.
109     *       shouldAbort = false;
110     *
111     *       // Get the post-read response control for the first modify operation.
112     *       // It's the same process for the second, but this example is already
113     *       // long enough so we'll skip it.
114     *       Control[] controls = commitResult.getOperationResponseControls(
115     *            modifyResult1.getMessageID());
116     *       if (controls != null)
117     *       {
118     *         for (Control c : controls)
119     *         {
120     *           if (c instanceof PostReadResponseControl)
121     *           {
122     *             PostReadResponseControl postReadResponse =
123     *                  (PostReadResponseControl) c;
124     *             System.out.println("First entry after the modification:");
125     *             System.out.println(postReadResponse.getEntry().toLDIFString());
126     *           }
127     *         }
128     *       }
129     *     }
130     *     else
131     *     {
132     *       // The transaction failed for some reason.  The response should tell us
133     *       // whether it failed because of one of the operations.
134     *       int failedOpMessageID = commitResult.getFailedOpMessageID();
135     *       if (failedOpMessageID == modifyResult1.getMessageID())
136     *       {
137     *         System.err.println("The transaction failed because of a failure " +
138     *              "encountered while processing the first modification.");
139     *       }
140     *       else if (failedOpMessageID == modifyResult2.getMessageID())
141     *       {
142     *         System.err.println("The transaction failed because of a failure " +
143     *              "encountered while processing the second modification.");
144     *       }
145     *       else
146     *       {
147     *         System.err.println("The transaction failed for some reason other " +
148     *              "than either of the modify operations.");
149     *       }
150     *
151     *       throw new LDAPException(commitResult);
152     *     }
153     *   }
154     *   finally
155     *   {
156     *     if (shouldAbort)
157     *     {
158     *       // Setting the commit flag to false in the end transaction request will
159     *       // will cause the transaction to be aborted rather than committed.
160     *       EndTransactionExtendedRequest abortRequest =
161     *             new EndTransactionExtendedRequest(txnID, false);
162     *       connection.processExtendedOperation(abortRequest);
163     *     }
164     *   }
165     * </PRE>
166     */
167    @NotMutable()
168    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
169    public final class StartTransactionExtendedRequest
170           extends ExtendedRequest
171    {
172      /**
173       * The OID (1.3.6.1.1.21.1) for the start transaction extended request.
174       */
175      public static final String START_TRANSACTION_REQUEST_OID = "1.3.6.1.1.21.1";
176    
177    
178      /**
179       * The serial version UID for this serializable class.
180       */
181      private static final long serialVersionUID = 7382735226826929629L;
182    
183    
184    
185      // This is an ugly hack to prevent checkstyle from complaining about imports
186      // for classes that are needed by javadoc @link elements but aren't otherwise
187      // used in the class.  It appears that checkstyle does not recognize the use
188      // of these classes in javadoc @link elements so we must ensure that they are
189      // referenced elsewhere in the class to prevent checkstyle from complaining.
190      static
191      {
192        final TransactionSpecificationRequestControl c = null;
193      }
194    
195    
196    
197      /**
198       * Creates a new start transaction extended request.
199       */
200      public StartTransactionExtendedRequest()
201      {
202        super(START_TRANSACTION_REQUEST_OID);
203      }
204    
205    
206    
207      /**
208       * Creates a new start transaction extended request.
209       *
210       * @param  controls  The set of controls to include in the request.
211       */
212      public StartTransactionExtendedRequest(final Control[] controls)
213      {
214        super(START_TRANSACTION_REQUEST_OID, controls);
215      }
216    
217    
218    
219      /**
220       * Creates a new start transaction extended request from the provided generic
221       * extended request.
222       *
223       * @param  extendedRequest  The generic extended request to use to create this
224       *                          start transaction extended request.
225       *
226       * @throws  LDAPException  If a problem occurs while decoding the request.
227       */
228      public StartTransactionExtendedRequest(final ExtendedRequest extendedRequest)
229             throws LDAPException
230      {
231        super(extendedRequest);
232    
233        if (extendedRequest.hasValue())
234        {
235          throw new LDAPException(ResultCode.DECODING_ERROR,
236               ERR_START_TXN_REQUEST_HAS_VALUE.get());
237        }
238      }
239    
240    
241    
242      /**
243       * {@inheritDoc}
244       */
245      @Override()
246      public StartTransactionExtendedResult process(
247                  final LDAPConnection connection, final int depth)
248             throws LDAPException
249      {
250        final ExtendedResult extendedResponse = super.process(connection, depth);
251        return new StartTransactionExtendedResult(extendedResponse);
252      }
253    
254    
255    
256      /**
257       * {@inheritDoc}
258       */
259      @Override()
260      public StartTransactionExtendedRequest duplicate()
261      {
262        return duplicate(getControls());
263      }
264    
265    
266    
267      /**
268       * {@inheritDoc}
269       */
270      @Override()
271      public StartTransactionExtendedRequest duplicate(final Control[] controls)
272      {
273        final StartTransactionExtendedRequest r =
274             new StartTransactionExtendedRequest(controls);
275        r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
276        return r;
277      }
278    
279    
280    
281      /**
282       * {@inheritDoc}
283       */
284      @Override()
285      public String getExtendedRequestName()
286      {
287        return INFO_EXTENDED_REQUEST_NAME_START_TXN.get();
288      }
289    
290    
291    
292      /**
293       * {@inheritDoc}
294       */
295      @Override()
296      public void toString(final StringBuilder buffer)
297      {
298        buffer.append("StartTransactionExtendedRequest(");
299    
300        final Control[] controls = getControls();
301        if (controls.length > 0)
302        {
303          buffer.append("controls={");
304          for (int i=0; i < controls.length; i++)
305          {
306            if (i > 0)
307            {
308              buffer.append(", ");
309            }
310    
311            buffer.append(controls[i]);
312          }
313          buffer.append('}');
314        }
315    
316        buffer.append(')');
317      }
318    }