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