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