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