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.extensions;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.List;
043
044import com.unboundid.asn1.ASN1Element;
045import com.unboundid.asn1.ASN1OctetString;
046import com.unboundid.asn1.ASN1Sequence;
047import com.unboundid.ldap.sdk.Control;
048import com.unboundid.ldap.sdk.ExtendedResult;
049import com.unboundid.ldap.sdk.LDAPException;
050import com.unboundid.ldap.sdk.ResultCode;
051import com.unboundid.util.Debug;
052import com.unboundid.util.NotMutable;
053import com.unboundid.util.NotNull;
054import com.unboundid.util.Nullable;
055import com.unboundid.util.StaticUtils;
056import com.unboundid.util.ThreadSafety;
057import com.unboundid.util.ThreadSafetyLevel;
058
059import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
060
061
062
063/**
064 * <BLOCKQUOTE>
065 *   <B>NOTE:</B>  The use of interactive transactions is discouraged because it
066 *   can create conditions which are prone to deadlocks between operations that
067 *   may result in the cancellation of one or both operations.  It is strongly
068 *   recommended that standard LDAP transactions (which may be started using a
069 *   {@link com.unboundid.ldap.sdk.extensions.StartTransactionExtendedRequest})
070 *   or a multi-update extended operation be used instead.  Although they cannot
071 *   include arbitrary read operations, LDAP transactions and multi-update
072 *   operations may be used in conjunction with the
073 *   {@link com.unboundid.ldap.sdk.controls.AssertionRequestControl},
074 *   {@link com.unboundid.ldap.sdk.controls.PreReadRequestControl}, and
075 *   {@link com.unboundid.ldap.sdk.controls.PostReadRequestControl} to
076 *   incorporate some read capability into a transaction, and in conjunction
077 *   with the {@link com.unboundid.ldap.sdk.ModificationType#INCREMENT}
078 *   modification type to increment integer values without the need to know the
079 *   precise value before or after the operation (although the pre-read and/or
080 *   post-read controls may be used to determine that).
081 * </BLOCKQUOTE>
082 * This class implements a data structure for storing the information from an
083 * extended result for the start interactive transaction extended request.  It
084 * is able to decode a generic extended result to extract the transaction ID and
085 * base DNs that it may contain, if the operation was successful.
086 * <BR>
087 * <BLOCKQUOTE>
088 *   <B>NOTE:</B>  This class, and other classes within the
089 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
090 *   supported for use against Ping Identity, UnboundID, and
091 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
092 *   for proprietary functionality or for external specifications that are not
093 *   considered stable or mature enough to be guaranteed to work in an
094 *   interoperable way with other types of LDAP servers.
095 * </BLOCKQUOTE>
096 * <BR>
097 * See the documentation for the
098 * {@link StartInteractiveTransactionExtendedRequest} class for an example that
099 * demonstrates the use of interactive transactions.
100 */
101@NotMutable()
102@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
103public final class StartInteractiveTransactionExtendedResult
104       extends ExtendedResult
105{
106  /**
107   * The BER type for the {@code txnID} element of the response.
108   */
109  private static final byte TYPE_TXN_ID = (byte) 0x80;
110
111
112
113  /**
114   * The BER type for the {@code baseDNs} element of the response.
115   */
116  private static final byte TYPE_BASE_DNS = (byte) 0xA1;
117
118
119
120  /**
121   * The serial version UID for this serializable class.
122   */
123  private static final long serialVersionUID = 4010094216900393866L;
124
125
126
127  // The transaction ID returned by the server.
128  @Nullable private final ASN1OctetString transactionID;
129
130  // The list of base DNs returned by the server, if any.
131  @Nullable private final List<String> baseDNs;
132
133
134
135  /**
136   * Creates a new start interactive transaction extended result from the
137   * provided extended result.
138   *
139   * @param  extendedResult  The extended result to be decoded as a start
140   *                         interactive transaction extended result.  It must
141   *                         not be {@code null}.
142   *
143   * @throws  LDAPException  If a problem occurs while attempting to decode the
144   *                         provided extended result as a start interactive
145   *                         transaction extended result.
146   */
147  public StartInteractiveTransactionExtendedResult(
148              @NotNull final ExtendedResult extendedResult)
149         throws LDAPException
150  {
151    super(extendedResult);
152
153    if (! extendedResult.hasValue())
154    {
155      transactionID = null;
156      baseDNs       = null;
157      return;
158    }
159
160    final ASN1Sequence valueSequence;
161    try
162    {
163      final ASN1Element valueElement =
164           ASN1Element.decode(extendedResult.getValue().getValue());
165      valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
166    }
167    catch (final Exception e)
168    {
169      Debug.debugException(e);
170      throw new LDAPException(ResultCode.DECODING_ERROR,
171           ERR_START_INT_TXN_RESULT_VALUE_NOT_SEQUENCE.get(e.getMessage()), e);
172    }
173
174    ASN1OctetString txnID      = null;
175    List<String>    baseDNList = null;
176    for (final ASN1Element element : valueSequence.elements())
177    {
178      switch (element.getType())
179      {
180        case TYPE_TXN_ID:
181          txnID = ASN1OctetString.decodeAsOctetString(element);
182          break;
183        case TYPE_BASE_DNS:
184          try
185          {
186            final ASN1Sequence baseDNsSequence =
187                 ASN1Sequence.decodeAsSequence(element);
188            final ArrayList<String> dnList =
189                 new ArrayList<>(baseDNsSequence.elements().length);
190            for (final ASN1Element e : baseDNsSequence.elements())
191            {
192              dnList.add(ASN1OctetString.decodeAsOctetString(e).stringValue());
193            }
194            baseDNList = Collections.unmodifiableList(dnList);
195          }
196          catch (final Exception e)
197          {
198            Debug.debugException(e);
199            throw new LDAPException(ResultCode.DECODING_ERROR,
200                 ERR_START_INT_TXN_RESULT_BASE_DNS_NOT_SEQUENCE.get(
201                      e.getMessage()), e);
202          }
203          break;
204        default:
205          throw new LDAPException(ResultCode.DECODING_ERROR,
206               ERR_START_INT_TXN_RESULT_INVALID_ELEMENT.get(
207                    StaticUtils.toHex(element.getType())));
208      }
209    }
210
211    transactionID = txnID;
212    baseDNs       =  baseDNList;
213
214    if (transactionID == null)
215    {
216      throw new LDAPException(ResultCode.DECODING_ERROR,
217                              ERR_START_INT_TXN_RESULT_NO_TXN_ID.get());
218    }
219  }
220
221
222
223  /**
224   * Creates a new start interactive transaction extended result with the
225   * provided information.
226   *
227   * @param  messageID          The message ID for the LDAP message that is
228   *                            associated with this LDAP result.
229   * @param  resultCode         The result code from the response.
230   * @param  diagnosticMessage  The diagnostic message from the response, if
231   *                            available.
232   * @param  matchedDN          The matched DN from the response, if available.
233   * @param  referralURLs       The set of referral URLs from the response, if
234   *                            available.
235   * @param  transactionID      The transaction ID for this response, if
236   *                            available.
237   * @param  baseDNs            The list of base DNs for this response, if
238   *                            available.
239   * @param  responseControls   The set of controls from the response, if
240   *                            available.
241   */
242  public StartInteractiveTransactionExtendedResult(final int messageID,
243              @NotNull final ResultCode resultCode,
244              @Nullable final String diagnosticMessage,
245              @Nullable final String matchedDN,
246              @Nullable final String[] referralURLs,
247              @Nullable final ASN1OctetString transactionID,
248              @Nullable final List<String> baseDNs,
249              @Nullable final Control[] responseControls)
250  {
251    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
252          null, encodeValue(transactionID, baseDNs), responseControls);
253
254    this.transactionID = transactionID;
255
256    if (baseDNs == null)
257    {
258      this.baseDNs = null;
259    }
260    else
261    {
262      this.baseDNs =
263           Collections.unmodifiableList(new ArrayList<>(baseDNs));
264    }
265  }
266
267
268
269  /**
270   * Encodes the provided information into an ASN.1 octet string suitable for
271   * use as the value of this extended result.
272   *
273   * @param  transactionID  The transaction ID for this response, if available.
274   * @param  baseDNs        The list of base DNs for this response, if
275   *                        available.
276   *
277   * @return  The ASN.1 octet string containing the encoded value, or
278   *          {@code null} if no value should be used.
279   */
280  @Nullable()
281  private static ASN1OctetString encodeValue(
282                      @Nullable final ASN1OctetString transactionID,
283                      @Nullable final List<String> baseDNs)
284  {
285    if ((transactionID == null) && (baseDNs == null))
286    {
287      return null;
288    }
289
290    final ArrayList<ASN1Element> elements = new ArrayList<>(2);
291    if (transactionID != null)
292    {
293      elements.add(new ASN1OctetString(TYPE_TXN_ID, transactionID.getValue()));
294    }
295
296    if ((baseDNs != null) && (! baseDNs.isEmpty()))
297    {
298      final ArrayList<ASN1Element> baseDNElements =
299           new ArrayList<>(baseDNs.size());
300      for (final String s : baseDNs)
301      {
302        baseDNElements.add(new ASN1OctetString(s));
303      }
304      elements.add(new ASN1Sequence(TYPE_BASE_DNS, baseDNElements));
305    }
306
307    return new ASN1OctetString(new ASN1Sequence(elements).encode());
308  }
309
310
311
312  /**
313   * Retrieves the transaction ID for this start interactive transaction
314   * extended result, if available.
315   *
316   * @return  The transaction ID for this start interactive transaction extended
317   *          result, or {@code null} if none was provided.
318   */
319  @Nullable()
320  public ASN1OctetString getTransactionID()
321  {
322    return transactionID;
323  }
324
325
326
327  /**
328   * Retrieves the list of base DNs for this start interactive transaction
329   * extended result, if available.
330   *
331   * @return  The list of base DNs for this start interactive transaction
332   *          extended result, or {@code null} if no base DN list was provided.
333   */
334  @Nullable()
335  public List<String> getBaseDNs()
336  {
337    return baseDNs;
338  }
339
340
341
342  /**
343   * {@inheritDoc}
344   */
345  @Override()
346  @NotNull()
347  public String getExtendedResultName()
348  {
349    return INFO_EXTENDED_RESULT_NAME_START_INTERACTIVE_TXN.get();
350  }
351
352
353
354  /**
355   * {@inheritDoc}
356   */
357  @Override()
358  public void toString(@NotNull final StringBuilder buffer)
359  {
360    buffer.append("StartInteractiveTransactionExtendedResult(resultCode=");
361    buffer.append(getResultCode());
362
363    final int messageID = getMessageID();
364    if (messageID >= 0)
365    {
366      buffer.append(", messageID=");
367      buffer.append(messageID);
368    }
369
370    if (transactionID != null)
371    {
372      buffer.append(", transactionID='");
373      buffer.append(transactionID.stringValue());
374      buffer.append('\'');
375    }
376
377    if (baseDNs != null)
378    {
379      buffer.append(", baseDNs={");
380      for (int i=0; i < baseDNs.size(); i++)
381      {
382        if (i > 0)
383        {
384          buffer.append(", ");
385        }
386
387        buffer.append('\'');
388        buffer.append(baseDNs.get(i));
389        buffer.append('\'');
390      }
391      buffer.append('}');
392    }
393
394    final String diagnosticMessage = getDiagnosticMessage();
395    if (diagnosticMessage != null)
396    {
397      buffer.append(", diagnosticMessage='");
398      buffer.append(diagnosticMessage);
399      buffer.append('\'');
400    }
401
402    final String matchedDN = getMatchedDN();
403    if (matchedDN != null)
404    {
405      buffer.append(", matchedDN='");
406      buffer.append(matchedDN);
407      buffer.append('\'');
408    }
409
410    final String[] referralURLs = getReferralURLs();
411    if (referralURLs.length > 0)
412    {
413      buffer.append(", referralURLs={");
414      for (int i=0; i < referralURLs.length; i++)
415      {
416        if (i > 0)
417        {
418          buffer.append(", ");
419        }
420
421        buffer.append('\'');
422        buffer.append(referralURLs[i]);
423        buffer.append('\'');
424      }
425      buffer.append('}');
426    }
427
428    final Control[] responseControls = getResponseControls();
429    if (responseControls.length > 0)
430    {
431      buffer.append(", responseControls={");
432      for (int i=0; i < responseControls.length; i++)
433      {
434        if (i > 0)
435        {
436          buffer.append(", ");
437        }
438
439        buffer.append(responseControls[i]);
440      }
441      buffer.append('}');
442    }
443
444    buffer.append(')');
445  }
446}