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