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    import java.util.Collections;
027    import java.util.List;
028    
029    import com.unboundid.asn1.ASN1Boolean;
030    import com.unboundid.asn1.ASN1Element;
031    import com.unboundid.asn1.ASN1OctetString;
032    import com.unboundid.asn1.ASN1Sequence;
033    import com.unboundid.ldap.sdk.Control;
034    import com.unboundid.ldap.sdk.DecodeableControl;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.LDAPResult;
037    import com.unboundid.ldap.sdk.ResultCode;
038    import com.unboundid.ldap.sdk.unboundidds.extensions.
039                StartInteractiveTransactionExtendedRequest;
040    import com.unboundid.util.NotMutable;
041    import com.unboundid.util.ThreadSafety;
042    import com.unboundid.util.ThreadSafetyLevel;
043    
044    import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
045    import static com.unboundid.util.StaticUtils.*;
046    
047    
048    
049    /**
050     * <BLOCKQUOTE>
051     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
052     *   LDAP SDK for Java.  It is not available for use in applications that
053     *   include only the Standard Edition of the LDAP SDK, and is not supported for
054     *   use in conjunction with non-UnboundID products.
055     * </BLOCKQUOTE>
056     * This class defines an interactive transaction specification response control,
057     * which will be included in the server's response to an operation that included
058     * the {@link InteractiveTransactionSpecificationRequestControl}.  It provides
059     * information about the state of the transaction, which may include:
060     * <UL>
061     *   <LI><CODE>transactionValid</CODE> -- Indicates whether the transaction is
062     *       still valid in the server.  This should be checked if the associated
063     *       operation did not complete successfully.</LI>
064     *   <LI><CODE>baseDNs</CODE> -- This may specify the set of base DNs below
065     *       which the client is allowed to request operations as part of this
066     *       transaction.  It may be absent if there are no restrictions on which
067     *       base DNs may be used, or if it has not changed since the last
068     *       response within this transaction.</LI>
069     * </UL>
070     * See the documentation in the
071     * {@link StartInteractiveTransactionExtendedRequest} class for an example of
072     * processing interactive transactions.
073     */
074    @NotMutable()
075    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
076    public final class InteractiveTransactionSpecificationResponseControl
077           extends Control
078           implements DecodeableControl
079    {
080      /**
081       * The OID (1.3.6.1.4.1.30221.2.5.4) for the interactive transaction
082       * specification response control.
083       */
084      public static final String
085           INTERACTIVE_TRANSACTION_SPECIFICATION_RESPONSE_OID =
086           "1.3.6.1.4.1.30221.2.5.4";
087    
088    
089    
090      /**
091       * The BER type for the {@code transactionValid} element of the control value.
092       */
093      private static final byte TYPE_TXN_VALID = (byte) 0x80;
094    
095    
096    
097      /**
098       * The BER type for the {@code baseDNs} element of the control value.
099       */
100      private static final byte TYPE_BASE_DNS = (byte) 0xA1;
101    
102    
103    
104      /**
105       * The serial version UID for this serializable class.
106       */
107      private static final long serialVersionUID = -4323085263241417543L;
108    
109    
110    
111      // The flag that indicates whether the associated transaction is still valid.
112      private final boolean transactionValid;
113    
114      // The set of base DNs that may be targeted by this transaction.
115      private final List<String> baseDNs;
116    
117    
118    
119      // This is an ugly hack to prevent checkstyle from complaining about imports
120      // for classes that are needed by javadoc @link elements but aren't otherwise
121      // used in the class.  It appears that checkstyle does not recognize the use
122      // of these classes in javadoc @link elements so we must ensure that they are
123      // referenced elsewhere in the class to prevent checkstyle from complaining.
124      static
125      {
126        final StartInteractiveTransactionExtendedRequest r = null;
127      }
128    
129    
130    
131      /**
132       * Creates a new empty control instance that is intended to be used only for
133       * decoding controls via the {@code DecodeableControl} interface.
134       */
135      InteractiveTransactionSpecificationResponseControl()
136      {
137        transactionValid = false;
138        baseDNs          = null;
139      }
140    
141    
142    
143      /**
144       * Creates a new interactive transaction specification response control with
145       * the provided information.  It will not be marked critical.
146       *
147       * @param  transactionValid  Indicates whether the associated transaction is
148       *                           still valid.
149       * @param  baseDNs           The set of base DNs that may be targeted over the
150       *                           course of the transaction.  It may be
151       *                           {@code null} if there are no restrictions or the
152       *                           set of restrictions has not changed since the
153       *                           last response.
154       */
155      public InteractiveTransactionSpecificationResponseControl(
156                  final boolean transactionValid, final List<String> baseDNs)
157      {
158        super(INTERACTIVE_TRANSACTION_SPECIFICATION_RESPONSE_OID, false,
159              encodeValue(transactionValid, baseDNs));
160    
161        this.transactionValid = transactionValid;
162    
163        if (baseDNs == null)
164        {
165          this.baseDNs = null;
166        }
167        else
168        {
169          this.baseDNs =
170               Collections.unmodifiableList(new ArrayList<String>(baseDNs));
171        }
172      }
173    
174    
175    
176      /**
177       * Creates a new interactive transaction specification response control with
178       * the provided information.
179       *
180       * @param  oid         The OID for the control.
181       * @param  isCritical  Indicates whether the control should be marked
182       *                     critical.
183       * @param  value       The encoded value for the control.  This may be
184       *                     {@code null} if no value was provided.
185       *
186       * @throws  LDAPException  If the provided control cannot be decoded as an
187       *                         interactive transaction specification response
188       *                         control.
189       */
190      public InteractiveTransactionSpecificationResponseControl(final String oid,
191                  final boolean isCritical, final ASN1OctetString value)
192             throws LDAPException
193      {
194        super(oid, isCritical, value);
195    
196        if (value == null)
197        {
198          throw new LDAPException(ResultCode.DECODING_ERROR,
199                                  ERR_INT_TXN_RESPONSE_NO_VALUE.get());
200        }
201    
202        final ASN1Element[] elements;
203        try
204        {
205          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
206          elements = ASN1Sequence.decodeAsSequence(valueElement).elements();
207        }
208        catch(Exception e)
209        {
210          throw new LDAPException(ResultCode.DECODING_ERROR,
211                                  ERR_INT_TXN_RESPONSE_VALUE_NOT_SEQUENCE.get(
212                                       e.getMessage()), e);
213        }
214    
215        Boolean isValid = null;
216        List<String> baseDNList = null;
217    
218        for (final ASN1Element element : elements)
219        {
220          switch (element.getType())
221          {
222            case TYPE_TXN_VALID:
223              try
224              {
225                isValid = ASN1Boolean.decodeAsBoolean(element).booleanValue();
226              }
227              catch (Exception e)
228              {
229                throw new LDAPException(ResultCode.DECODING_ERROR,
230                     ERR_INT_TXN_RESPONSE_TXN_VALID_NOT_BOOLEAN.get(e.getMessage()),
231                     e);
232              }
233              break;
234            case TYPE_BASE_DNS:
235              try
236              {
237                final ASN1Sequence s = ASN1Sequence.decodeAsSequence(element);
238                baseDNList = new ArrayList<String>(s.elements().length);
239                for (final ASN1Element e : s.elements())
240                {
241                  baseDNList.add(
242                       ASN1OctetString.decodeAsOctetString(e).stringValue());
243                }
244              }
245              catch (Exception e)
246              {
247                throw new LDAPException(ResultCode.DECODING_ERROR,
248                     ERR_INT_TXN_RESPONSE_BASE_DNS_NOT_SEQUENCE.get(e.getMessage()),
249                     e);
250              }
251              break;
252            default:
253              throw new LDAPException(ResultCode.DECODING_ERROR,
254                                      ERR_INT_TXN_RESPONSE_INVALID_ELEMENT_TYPE.get(
255                                           toHex(element.getType())));
256          }
257        }
258    
259        if (isValid == null)
260        {
261          throw new LDAPException(ResultCode.DECODING_ERROR,
262                                  ERR_INT_TXN_RESPONSE_NO_TXN_VALID.get());
263        }
264    
265        transactionValid = isValid;
266    
267        if (baseDNList == null)
268        {
269          baseDNs = null;
270        }
271        else
272        {
273          baseDNs = Collections.unmodifiableList(baseDNList);
274        }
275      }
276    
277    
278    
279      /**
280       * Encodes the provided information into an ASN.1 octet string suitable for
281       * use as the value of this control.
282       *
283       * @param  transactionValid  Indicates whether the associated transaction is
284       *                           still valid.
285       * @param  baseDNs           The set of base DNs that may be targeted over the
286       *                           course of the transaction.  It may be
287       *                           {@code null} if there are no restrictions or the
288       *                           set of restrictions has not changed since the
289       *                           last response.
290       *
291       * @return  The ASN1 octet string that may be used as the control value.
292       */
293      private static ASN1OctetString encodeValue(final boolean transactionValid,
294                                                 final List<String> baseDNs)
295      {
296        final ASN1Element[] elements;
297        if (baseDNs == null)
298        {
299          elements = new ASN1Element[]
300          {
301            new ASN1Boolean(TYPE_TXN_VALID, transactionValid)
302          };
303        }
304        else
305        {
306          final ASN1Element[] baseDNElements = new ASN1Element[baseDNs.size()];
307          for (int i=0; i < baseDNElements.length; i++)
308          {
309            baseDNElements[i] = new ASN1OctetString(baseDNs.get(i));
310          }
311    
312          elements = new ASN1Element[]
313          {
314            new ASN1Boolean(TYPE_TXN_VALID, transactionValid),
315            new ASN1Sequence(TYPE_BASE_DNS, baseDNElements)
316          };
317        }
318    
319        return new ASN1OctetString(new ASN1Sequence(elements).encode());
320      }
321    
322    
323    
324      /**
325       * {@inheritDoc}
326       */
327      public InteractiveTransactionSpecificationResponseControl decodeControl(
328                  final String oid, final boolean isCritical,
329                  final ASN1OctetString value)
330              throws LDAPException
331      {
332        return new InteractiveTransactionSpecificationResponseControl(oid,
333                        isCritical, value);
334      }
335    
336    
337    
338      /**
339       * Extracts an interactive transaction specification response control from the
340       * provided result.
341       *
342       * @param  result  The result from which to retrieve the interactive
343       *                 transaction specification response control.
344       *
345       * @return  The interactive transaction specification response control
346       *          contained in the provided result, or {@code null} if the result
347       *          did not contain an interactive transaction specification response
348       *          control.
349       *
350       * @throws  LDAPException  If a problem is encountered while attempting to
351       *                         decode the interactive transaction specification
352       *                         response control contained in the provided result.
353       */
354      public static InteractiveTransactionSpecificationResponseControl
355                         get(final LDAPResult result)
356             throws LDAPException
357      {
358        final Control c = result.getResponseControl(
359             INTERACTIVE_TRANSACTION_SPECIFICATION_RESPONSE_OID);
360        if (c == null)
361        {
362          return null;
363        }
364    
365        if (c instanceof InteractiveTransactionSpecificationResponseControl)
366        {
367          return (InteractiveTransactionSpecificationResponseControl) c;
368        }
369        else
370        {
371          return new InteractiveTransactionSpecificationResponseControl(c.getOID(),
372               c.isCritical(), c.getValue());
373        }
374      }
375    
376    
377    
378      /**
379       * Indicates whether the associated transaction is still valid on the server.
380       *
381       * @return  {@code true} if the associated transaction is still valid on the
382       *          server and may be used for future operations, or {@code false} if
383       *          the transaction has been aborted and may no longer be used.
384       */
385      public boolean transactionValid()
386      {
387        return transactionValid;
388      }
389    
390    
391    
392      /**
393       * Retrieves the set of base DNs below which operations which are part of the
394       * transaction may be performed.
395       *
396       * @return  The set of base DNs below which operations may be performed as
397       *          part of the transaction, or {@code null} if there are no
398       *          restrictions or if the set of restrictions has not changed since
399       *          the last response.
400       */
401      public List<String> getBaseDNs()
402      {
403        return baseDNs;
404      }
405    
406    
407    
408      /**
409       * {@inheritDoc}
410       */
411      @Override()
412      public String getControlName()
413      {
414        return INFO_CONTROL_NAME_INTERACTIVE_TXN_RESPONSE.get();
415      }
416    
417    
418    
419      /**
420       * {@inheritDoc}
421       */
422      @Override()
423      public void toString(final StringBuilder buffer)
424      {
425        buffer.append("InteractiveTransactionSpecificationResponseControl(");
426        buffer.append("transactionValid=");
427        buffer.append(transactionValid);
428        buffer.append(", baseDNs=");
429        if (baseDNs == null)
430        {
431          buffer.append("null");
432        }
433        else
434        {
435          buffer.append('{');
436          for (int i=0; i < baseDNs.size(); i++)
437          {
438            if (i > 0)
439            {
440              buffer.append(", ");
441            }
442    
443            buffer.append('\'');
444            buffer.append(baseDNs.get(i));
445            buffer.append('\'');
446          }
447          buffer.append('}');
448        }
449    
450        buffer.append(", isCritical=");
451        buffer.append(isCritical());
452        buffer.append(')');
453      }
454    }