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.controls;
022    
023    
024    
025    import java.util.ArrayList;
026    
027    import com.unboundid.asn1.ASN1Boolean;
028    import com.unboundid.asn1.ASN1Element;
029    import com.unboundid.asn1.ASN1OctetString;
030    import com.unboundid.asn1.ASN1Sequence;
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.ldap.sdk.LDAPException;
033    import com.unboundid.ldap.sdk.ResultCode;
034    import com.unboundid.ldap.sdk.unboundidds.extensions.
035                StartInteractiveTransactionExtendedRequest;
036    import com.unboundid.ldap.sdk.unboundidds.extensions.
037                StartInteractiveTransactionExtendedResult;
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.controls.ControlMessages.*;
043    import static com.unboundid.util.StaticUtils.*;
044    import static com.unboundid.util.Validator.*;
045    
046    
047    
048    /**
049     * <BLOCKQUOTE>
050     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
051     *   LDAP SDK for Java.  It is not available for use in applications that
052     *   include only the Standard Edition of the LDAP SDK, and is not supported for
053     *   use in conjunction with non-UnboundID products.
054     * </BLOCKQUOTE>
055     * This class provides an implementation of the interactive transaction
056     * specification request control, which may be used to indicate that the
057     * associated operation is part of an interactive transaction.  It may be used
058     * in conjunction with add, compare, delete, modify, modify DN, and search
059     * requests, as well as some types of extended requests.  The transaction should
060     * be created with the start interactive transaction extended request, and the
061     * end interactive transaction extended request may be used to commit or abort
062     * the associated transaction.
063     * <BR><BR>
064     * The elements of the interactive transaction specification request control may
065     * include:
066     * <UL>
067     *   <LI><CODE>txnID</CODE> -- The transaction ID for the transaction, which was
068     *       obtained from a previous
069     *       {@link StartInteractiveTransactionExtendedResult}.</LI>
070     *   <LI><CODE>abortOnFailure</CODE> -- Indicates whether the transaction should
071     *       be aborted if the request associated with this control does not
072     *       complete successfully.</LI>
073     *   <LI><CODE>writeLock</CODE> -- Indicates whether the target entry may be
074     *       altered by this or a subsequent operation which is part of the
075     *       transaction.  It should generally be {@code false} only for read
076     *       operations in which it is known that the target entry will not be
077     *       altered by a subsequent operation.</LI>
078     * </UL>
079     * See the documentation for the
080     * {@link StartInteractiveTransactionExtendedRequest} class for an example of
081     * processing an interactive transaction.
082     */
083    @NotMutable()
084    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
085    public final class InteractiveTransactionSpecificationRequestControl
086           extends Control
087    {
088      /**
089       * The OID (1.3.6.1.4.1.30221.2.5.4) for the interactive transaction
090       * specification request control.
091       */
092      public static final String INTERACTIVE_TRANSACTION_SPECIFICATION_REQUEST_OID =
093           "1.3.6.1.4.1.30221.2.5.4";
094    
095    
096    
097      /**
098       * The BER type for the {@code txnID} element of the control value.
099       */
100      private static final byte TYPE_TXN_ID = (byte) 0x80;
101    
102    
103    
104      /**
105       * The BER type for the {@code abortOnFailure} element of the control value.
106       */
107      private static final byte TYPE_ABORT_ON_FAILURE = (byte) 0x81;
108    
109    
110    
111      /**
112       * The BER type for the {@code writeLock} element of the control value.
113       */
114      private static final byte TYPE_WRITE_LOCK = (byte) 0x82;
115    
116    
117    
118      /**
119       * The serial version UID for this serializable class.
120       */
121      private static final long serialVersionUID = -6473934815135786621L;
122    
123    
124    
125      // The transaction ID for the associated transaction.
126      private final ASN1OctetString transactionID;
127    
128      // Indicates whether the transaction should be aborted if the associated
129      // operation does not complete successfully.
130      private final boolean abortOnFailure;
131    
132      // Indicates whether the server should attempt to obtain a write lock on the
133      // target entry if the associated operation is a read operation.
134      private final boolean writeLock;
135    
136    
137    
138      // This is an ugly hack to prevent checkstyle from complaining about imports
139      // for classes that are needed by javadoc @link elements but aren't otherwise
140      // used in the class.  It appears that checkstyle does not recognize the use
141      // of these classes in javadoc @link elements so we must ensure that they are
142      // referenced elsewhere in the class to prevent checkstyle from complaining.
143      static
144      {
145        final StartInteractiveTransactionExtendedRequest r1 = null;
146        final StartInteractiveTransactionExtendedResult  r2 = null;
147      }
148    
149    
150    
151      /**
152       * Creates a new interactive transaction specification request control with
153       * the provided transaction ID.  The server will attempt to keep the
154       * transaction active in the event of a failure and will obtain write locks on
155       * targeted entries.
156       *
157       * @param  transactionID   The transaction ID for the associated transaction,
158       *                         as obtained from the start interactive transaction
159       *                         extended operation.  It must not be {@code null}.
160       */
161      public InteractiveTransactionSpecificationRequestControl(
162                  final ASN1OctetString transactionID)
163      {
164        this(transactionID, false, true);
165      }
166    
167    
168    
169      /**
170       * Creates a new interactive transaction specification request control with
171       * the provided information.
172       *
173       * @param  transactionID   The transaction ID for the associated transaction,
174       *                         as obtained from the start interactive transaction
175       *                         extended operation.  It must not be {@code null}.
176       * @param  abortOnFailure  Indicates whether the transaction should be aborted
177       *                         if the associated operation does not complete
178       *                         successfully.
179       * @param  writeLock       Indicates whether the server should attempt to
180       *                         obtain a write lock on the target entry.  This
181       *                         should only be {@code false} if the associated
182       *                         operation is a search or compare and it is known
183       *                         that the target entry will not be updated later in
184       *                         the transaction.
185       */
186      public InteractiveTransactionSpecificationRequestControl(
187                  final ASN1OctetString transactionID, final boolean abortOnFailure,
188                  final boolean writeLock)
189      {
190        super(INTERACTIVE_TRANSACTION_SPECIFICATION_REQUEST_OID, true,
191              encodeValue(transactionID, abortOnFailure, writeLock));
192    
193        this.transactionID  = transactionID;
194        this.abortOnFailure = abortOnFailure;
195        this.writeLock      = writeLock;
196      }
197    
198    
199    
200      /**
201       * Creates a new interactive transaction specification request control which
202       * is decoded from the provided generic control.
203       *
204       * @param  control  The generic control to be decoded as an interactive
205       *                  transaction specification request control.
206       *
207       * @throws  LDAPException  If the provided control cannot be decoded as an
208       *                         interactive transaction specification request
209       *                         control.
210       */
211      public InteractiveTransactionSpecificationRequestControl(
212                  final Control control)
213             throws LDAPException
214      {
215        super(control);
216    
217        if (! control.hasValue())
218        {
219          throw new LDAPException(ResultCode.DECODING_ERROR,
220                                  ERR_INT_TXN_REQUEST_NO_VALUE.get());
221        }
222    
223        final ASN1Element[] elements;
224        try
225        {
226          final ASN1Element e = ASN1Element.decode(control.getValue().getValue());
227          elements = ASN1Sequence.decodeAsSequence(e).elements();
228        }
229        catch (Exception e)
230        {
231          throw new LDAPException(ResultCode.DECODING_ERROR,
232               ERR_INT_TXN_REQUEST_VALUE_NOT_SEQUENCE.get(e.getMessage()), e);
233        }
234    
235        ASN1OctetString txnID = null;
236        boolean shouldAbortOnFailure = false;
237        boolean shouldWriteLock = true;
238    
239        for (final ASN1Element element : elements)
240        {
241          switch (element.getType())
242          {
243            case TYPE_TXN_ID:
244              txnID = ASN1OctetString.decodeAsOctetString(element);
245              break;
246            case TYPE_ABORT_ON_FAILURE:
247              try
248              {
249                shouldAbortOnFailure =
250                     ASN1Boolean.decodeAsBoolean(element).booleanValue();
251              }
252              catch (Exception e)
253              {
254                throw new LDAPException(ResultCode.DECODING_ERROR,
255                     ERR_INT_TXN_REQUEST_ABORT_ON_FAILURE_NOT_BOOLEAN.get(
256                          e.getMessage()), e);
257              }
258              break;
259            case TYPE_WRITE_LOCK:
260              try
261              {
262                shouldWriteLock =
263                     ASN1Boolean.decodeAsBoolean(element).booleanValue();
264              }
265              catch (Exception e)
266              {
267                throw new LDAPException(ResultCode.DECODING_ERROR,
268                     ERR_INT_TXN_REQUEST_WRITE_LOCK_NOT_BOOLEAN.get(e.getMessage()),
269                     e);
270              }
271              break;
272            default:
273              throw new LDAPException(ResultCode.DECODING_ERROR,
274                   ERR_INT_TXN_REQUEST_INVALID_ELEMENT_TYPE.get(
275                        toHex(element.getType())));
276          }
277        }
278    
279        if (txnID == null)
280        {
281          throw new LDAPException(ResultCode.DECODING_ERROR,
282                                  ERR_INT_TXN_REQUEST_NO_TXN_ID.get());
283        }
284    
285        transactionID  = txnID;
286        abortOnFailure = shouldAbortOnFailure;
287        writeLock      = shouldWriteLock;
288      }
289    
290    
291    
292      /**
293       * Encodes the provided information into an ASN.1 octet string suitable for
294       * use as the value of this control.
295       *
296       * @param  transactionID   The transaction ID for the associated transaction,
297       *                         as obtained from the start interactive transaction
298       *                         extended operation.  It must not be {@code null}.
299       * @param  abortOnFailure  Indicates whether the transaction should be aborted
300       *                         if the associated operation does not complete
301       *                         successfully.
302       * @param  writeLock       Indicates whether the server should attempt to
303       *                         obtain a write lock on the target entry.  This
304       *                         should only be {@code false} if the associated
305       *                         operation is a search or compare and it is known
306       *                         that the target entry will not be updated later in
307       *                         the transaction.
308       *
309       * @return  The ASN.1 octet string containing the encoded value for this
310       *          control.
311       */
312      private static ASN1OctetString encodeValue(
313                     final ASN1OctetString transactionID,
314                     final boolean abortOnFailure, final boolean writeLock)
315      {
316        ensureNotNull(transactionID);
317    
318        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3);
319        elements.add(new ASN1OctetString(TYPE_TXN_ID, transactionID.getValue()));
320    
321        if (abortOnFailure)
322        {
323          elements.add(new ASN1Boolean(TYPE_ABORT_ON_FAILURE, abortOnFailure));
324        }
325    
326        if (! writeLock)
327        {
328          elements.add(new ASN1Boolean(TYPE_WRITE_LOCK, writeLock));
329        }
330    
331        return new ASN1OctetString(new ASN1Sequence(elements).encode());
332      }
333    
334    
335    
336      /**
337       * Retrieves the transaction ID for the associated transaction.
338       *
339       * @return  The transaction ID for the associated transaction.
340       */
341      public ASN1OctetString getTransactionID()
342      {
343        return transactionID;
344      }
345    
346    
347    
348      /**
349       * Indicates whether the transaction should be aborted if the associated
350       * operation does not complete successfully.
351       *
352       * @return  {@code true} if the transaction should be aborted if the
353       *          associated operation does not complete successfully, or
354       *          {@code false} if the server should attempt to keep the transaction
355       *          active if the associated operation does not complete successfully.
356       */
357      public boolean abortOnFailure()
358      {
359        return abortOnFailure;
360      }
361    
362    
363    
364      /**
365       * Indicates whether the server should attempt to obtain a write lock on
366       * entries targeted by the associated operation.
367       *
368       * @return  {@code true} if the server should attempt to obtain a write lock
369       *          on entries targeted by the associated operation, or {@code false}
370       *          if a read lock is acceptable as the entries are not expected to
371       *          be altered later in the transaction.
372       */
373      public boolean writeLock()
374      {
375        return writeLock;
376      }
377    
378    
379    
380      /**
381       * {@inheritDoc}
382       */
383      @Override()
384      public String getControlName()
385      {
386        return INFO_CONTROL_NAME_INTERACTIVE_TXN_REQUEST.get();
387      }
388    
389    
390    
391      /**
392       * {@inheritDoc}
393       */
394      @Override()
395      public void toString(final StringBuilder buffer)
396      {
397        buffer.append("InteractiveTransactionSpecificationRequestControl(" +
398                      "transactionID='");
399        buffer.append(transactionID.stringValue());
400        buffer.append("', abortOnFailure=");
401        buffer.append(abortOnFailure);
402        buffer.append(", writeLock=");
403        buffer.append(writeLock);
404        buffer.append(')');
405      }
406    }