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