001/*
002 * Copyright 2007-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2019 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
024import java.util.ArrayList;
025import java.util.Map;
026import java.util.TreeMap;
027
028import com.unboundid.asn1.ASN1Constants;
029import com.unboundid.asn1.ASN1Element;
030import com.unboundid.asn1.ASN1Exception;
031import com.unboundid.asn1.ASN1Integer;
032import com.unboundid.asn1.ASN1OctetString;
033import com.unboundid.asn1.ASN1Sequence;
034import com.unboundid.ldap.sdk.Control;
035import com.unboundid.ldap.sdk.ExtendedResult;
036import com.unboundid.ldap.sdk.LDAPException;
037import com.unboundid.ldap.sdk.ResultCode;
038import com.unboundid.util.Debug;
039import com.unboundid.util.NotMutable;
040import com.unboundid.util.StaticUtils;
041import com.unboundid.util.ThreadSafety;
042import com.unboundid.util.ThreadSafetyLevel;
043
044import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
045
046
047
048/**
049 * This class provides an implementation of the end batched transaction extended
050 * result.  It is able to decode a generic extended result to extract the
051 * appropriate response information.
052 * <BR>
053 * <BLOCKQUOTE>
054 *   <B>NOTE:</B>  This class, and other classes within the
055 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
056 *   supported for use against Ping Identity, UnboundID, and
057 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
058 *   for proprietary functionality or for external specifications that are not
059 *   considered stable or mature enough to be guaranteed to work in an
060 *   interoperable way with other types of LDAP servers.
061 * </BLOCKQUOTE>
062 * <BR>
063 * The end batched transaction result may include two elements:
064 * <UL>
065 *   <LI>{@code failedOpMessageID} -- The message ID associated with the LDAP
066 *       request that caused the transaction to fail.  It will be "{@code -1}"
067 *       if the transaction was committed successfully.</LI>
068 *   <LI>{@code opResponseControls} -- A map containing the response controls
069 *       associated with each of the operations processed as part of the
070 *       transaction, mapped from the message ID of the associated request to
071 *       the array of response controls for that operation.  If there are no
072 *       response controls for a given request, then it will not be included in
073 *       the map.</LI>
074 * </UL>
075 * Note that both of these elements reference the LDAP message ID for the
076 * associated request.  Normally, this is not something that developers using
077 * the UnboundID LDAP SDK for Java need to access since it is handled behind the
078 * scenes, but the LDAP message ID for an operation is available through the
079 * {@link com.unboundid.ldap.sdk.LDAPResult#getMessageID} method in the response
080 * for that operation.  When processing operations that are part of a batched,
081 * transaction it may be desirable to keep references to the associated requests
082 * mapped by message ID so that they can be available if necessary for the
083 * {@code failedOpMessageID} and/or {@code opResponseControls} elements.
084 * <BR><BR>
085 * See the documentation for the {@link StartBatchedTransactionExtendedRequest}
086 * for an example of performing a batched transaction.
087 */
088@NotMutable()
089@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
090public final class EndBatchedTransactionExtendedResult
091       extends ExtendedResult
092{
093  /**
094   * The serial version UID for this serializable class.
095   */
096  private static final long serialVersionUID = 1514265185948328221L;
097
098
099
100  // The message ID for the operation that failed, if applicable.
101  private final int failedOpMessageID;
102
103  // A mapping of the response controls for the operations performed as part of
104  // the transaction.
105  private final TreeMap<Integer,Control[]> opResponseControls;
106
107
108
109  /**
110   * Creates a new end batched transaction extended result from the provided
111   * extended result.
112   *
113   * @param  extendedResult  The extended result to be decoded as an end batched
114   *                         transaction extended result.  It must not be
115   *                         {@code null}.
116   *
117   * @throws  LDAPException  If a problem occurs while attempting to decode the
118   *                         provided extended result as an end batched
119   *                         transaction extended result.
120   */
121  public EndBatchedTransactionExtendedResult(
122              final ExtendedResult extendedResult)
123         throws LDAPException
124  {
125    super(extendedResult);
126
127    opResponseControls = new TreeMap<>();
128
129    final ASN1OctetString value = extendedResult.getValue();
130    if (value == null)
131    {
132      failedOpMessageID = -1;
133      return;
134    }
135
136    final ASN1Sequence valueSequence;
137    try
138    {
139      final ASN1Element valueElement = ASN1Element.decode(value.getValue());
140      valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
141    }
142    catch (final ASN1Exception ae)
143    {
144      Debug.debugException(ae);
145      throw new LDAPException(ResultCode.DECODING_ERROR,
146           ERR_END_TXN_RESPONSE_VALUE_NOT_SEQUENCE.get(ae.getMessage()), ae);
147    }
148
149    final ASN1Element[] valueElements = valueSequence.elements();
150    if (valueElements.length == 0)
151    {
152      failedOpMessageID = -1;
153      return;
154    }
155    else if (valueElements.length > 2)
156    {
157      throw new LDAPException(ResultCode.DECODING_ERROR,
158           ERR_END_TXN_RESPONSE_INVALID_ELEMENT_COUNT.get(
159                valueElements.length));
160    }
161
162    int msgID = -1;
163    for (final ASN1Element e : valueElements)
164    {
165      if (e.getType() == ASN1Constants.UNIVERSAL_INTEGER_TYPE)
166      {
167        try
168        {
169          msgID = ASN1Integer.decodeAsInteger(e).intValue();
170        }
171        catch (final ASN1Exception ae)
172        {
173          Debug.debugException(ae);
174          throw new LDAPException(ResultCode.DECODING_ERROR,
175               ERR_END_TXN_RESPONSE_CANNOT_DECODE_MSGID.get(ae), ae);
176        }
177      }
178      else if (e.getType() == ASN1Constants.UNIVERSAL_SEQUENCE_TYPE)
179      {
180        decodeOpControls(e, opResponseControls);
181      }
182      else
183      {
184        throw new LDAPException(ResultCode.DECODING_ERROR,
185             ERR_END_TXN_RESPONSE_INVALID_TYPE.get(
186                  StaticUtils.toHex(e.getType())));
187      }
188    }
189
190    failedOpMessageID = msgID;
191  }
192
193
194
195  /**
196   * Creates a new end batched transaction extended result with the provided
197   * information.
198   *
199   * @param  messageID           The message ID for the LDAP message that is
200   *                             associated with this LDAP result.
201   * @param  resultCode          The result code from the response.
202   * @param  diagnosticMessage   The diagnostic message from the response, if
203   *                             available.
204   * @param  matchedDN           The matched DN from the response, if available.
205   * @param  referralURLs        The set of referral URLs from the response, if
206   *                             available.
207   * @param  failedOpMessageID   The message ID for the operation that failed,
208   *                             or {@code null} if there was no failure.
209   * @param  opResponseControls  A map containing the response controls for each
210   *                             operation, indexed by message ID.  It may be
211   *                             {@code null} if there were no response
212   *                             controls.
213   * @param  responseControls    The set of controls from the response, if
214   *                             available.
215   */
216  public EndBatchedTransactionExtendedResult(final int messageID,
217              final ResultCode resultCode, final String diagnosticMessage,
218              final String matchedDN, final String[] referralURLs,
219              final Integer failedOpMessageID,
220              final Map<Integer,Control[]> opResponseControls,
221              final Control[] responseControls)
222  {
223    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
224          null, encodeValue(failedOpMessageID, opResponseControls),
225                            responseControls);
226
227    if ((failedOpMessageID == null) || (failedOpMessageID <= 0))
228    {
229      this.failedOpMessageID = -1;
230    }
231    else
232    {
233      this.failedOpMessageID = failedOpMessageID;
234    }
235
236    if (opResponseControls == null)
237    {
238      this.opResponseControls = new TreeMap<>();
239    }
240    else
241    {
242      this.opResponseControls = new TreeMap<>(opResponseControls);
243    }
244  }
245
246
247
248  /**
249   * Decodes the provided ASN.1 element as an update controls sequence.  Each
250   * element of the sequence should itself be a sequence containing the message
251   * ID associated with the operation in which the control was returned and a
252   * sequence of the controls included in the response for that operation.
253   *
254   * @param  element     The ASN.1 element to be decoded.
255   * @param  controlMap  The map into which to place the decoded controls.
256   *
257   * @throws  LDAPException  If a problem occurs while attempting to decode the
258   *                         contents of the provided ASN.1 element.
259   */
260  private static void decodeOpControls(final ASN1Element element,
261                                       final Map<Integer,Control[]> controlMap)
262          throws LDAPException
263  {
264    final ASN1Sequence ctlsSequence;
265    try
266    {
267      ctlsSequence = ASN1Sequence.decodeAsSequence(element);
268    }
269    catch (final ASN1Exception ae)
270    {
271      Debug.debugException(ae);
272      throw new LDAPException(ResultCode.DECODING_ERROR,
273           ERR_END_TXN_RESPONSE_CONTROLS_NOT_SEQUENCE.get(ae), ae);
274    }
275
276    for (final ASN1Element e : ctlsSequence.elements())
277    {
278      final ASN1Sequence ctlSequence;
279      try
280      {
281        ctlSequence = ASN1Sequence.decodeAsSequence(e);
282      }
283      catch (final ASN1Exception ae)
284      {
285        Debug.debugException(ae);
286        throw new LDAPException(ResultCode.DECODING_ERROR,
287             ERR_END_TXN_RESPONSE_CONTROL_NOT_SEQUENCE.get(ae), ae);
288      }
289
290      final ASN1Element[] ctlSequenceElements = ctlSequence.elements();
291      if (ctlSequenceElements.length != 2)
292      {
293        throw new LDAPException(ResultCode.DECODING_ERROR,
294             ERR_END_TXN_RESPONSE_CONTROL_INVALID_ELEMENT_COUNT.get(
295                  ctlSequenceElements.length));
296      }
297
298      final int msgID;
299      try
300      {
301        msgID = ASN1Integer.decodeAsInteger(ctlSequenceElements[0]).intValue();
302      }
303      catch (final ASN1Exception ae)
304      {
305        Debug.debugException(ae);
306        throw new LDAPException(ResultCode.DECODING_ERROR,
307             ERR_END_TXN_RESPONSE_CONTROL_MSGID_NOT_INT.get(ae), ae);
308      }
309
310      final ASN1Sequence controlsSequence;
311      try
312      {
313        controlsSequence =
314             ASN1Sequence.decodeAsSequence(ctlSequenceElements[1]);
315      }
316      catch (final ASN1Exception ae)
317      {
318        Debug.debugException(ae);
319        throw new LDAPException(ResultCode.DECODING_ERROR,
320             ERR_END_TXN_RESPONSE_CONTROLS_ELEMENT_NOT_SEQUENCE.get(ae), ae);
321      }
322
323      final Control[] controls = Control.decodeControls(controlsSequence);
324      if (controls.length == 0)
325      {
326        continue;
327      }
328
329      controlMap.put(msgID, controls);
330    }
331  }
332
333
334
335  /**
336   * Encodes the provided information into an appropriate value for this
337   * control.
338   *
339   * @param  failedOpMessageID   The message ID for the operation that failed,
340   *                             or {@code null} if there was no failure.
341   * @param  opResponseControls  A map containing the response controls for each
342   *                             operation, indexed by message ID.  It may be
343   *                             {@code null} if there were no response
344   *                             controls.
345   *
346   * @return  An ASN.1 octet string containing the encoded value for this
347   *          control, or {@code null} if there should not be a value.
348   */
349  private static ASN1OctetString encodeValue(final Integer failedOpMessageID,
350                      final Map<Integer,Control[]> opResponseControls)
351  {
352    if ((failedOpMessageID == null) && (opResponseControls == null))
353    {
354      return null;
355    }
356
357    final ArrayList<ASN1Element> elements = new ArrayList<>(2);
358    if (failedOpMessageID != null)
359    {
360      elements.add(new ASN1Integer(failedOpMessageID));
361    }
362
363    if ((opResponseControls != null) && (! opResponseControls.isEmpty()))
364    {
365      final ArrayList<ASN1Element> controlElements = new ArrayList<>(10);
366      for (final Map.Entry<Integer,Control[]> e : opResponseControls.entrySet())
367      {
368        final ASN1Element[] ctlElements =
369        {
370          new ASN1Integer(e.getKey()),
371          Control.encodeControls(e.getValue())
372        };
373        controlElements.add(new ASN1Sequence(ctlElements));
374      }
375
376      elements.add(new ASN1Sequence(controlElements));
377    }
378
379    return new ASN1OctetString(new ASN1Sequence(elements).encode());
380  }
381
382
383
384  /**
385   * Retrieves the message ID of the operation that caused the transaction
386   * processing to fail, if applicable.
387   *
388   * @return  The message ID of the operation that caused the transaction
389   *          processing to fail, or -1 if no message ID was included in the
390   *          end transaction response.
391   */
392  public int getFailedOpMessageID()
393  {
394    return failedOpMessageID;
395  }
396
397
398
399  /**
400   * Retrieves the set of response controls returned by the operations
401   * processed as part of the transaction.  The value returned will contain a
402   * mapping between the message ID of the associated request message and a list
403   * of the response controls for that operation.
404   *
405   * @return  The set of response controls returned by the operations processed
406   *          as part of the transaction.  It may be an empty map if none of the
407   *          operations had any response controls.
408   */
409  public Map<Integer,Control[]> getOperationResponseControls()
410  {
411    return opResponseControls;
412  }
413
414
415
416  /**
417   * Retrieves the set of response controls returned by the specified operation
418   * processed as part of the transaction.
419   *
420   * @param  messageID  The message ID of the operation for which to retrieve
421   *                    the response controls.
422   *
423   * @return  The response controls for the specified operation, or
424   *          {@code null} if there were no controls returned for the specified
425   *          operation.
426   */
427  public Control[] getOperationResponseControls(final int messageID)
428  {
429    return opResponseControls.get(messageID);
430  }
431
432
433
434  /**
435   * {@inheritDoc}
436   */
437  @Override()
438  public String getExtendedResultName()
439  {
440    return INFO_EXTENDED_RESULT_NAME_END_BATCHED_TXN.get();
441  }
442
443
444
445  /**
446   * Appends a string representation of this extended result to the provided
447   * buffer.
448   *
449   * @param  buffer  The buffer to which a string representation of this
450   *                 extended result will be appended.
451   */
452  @Override()
453  public void toString(final StringBuilder buffer)
454  {
455    buffer.append("EndBatchedTransactionExtendedResult(resultCode=");
456    buffer.append(getResultCode());
457
458    final int messageID = getMessageID();
459    if (messageID >= 0)
460    {
461      buffer.append(", messageID=");
462      buffer.append(messageID);
463    }
464
465    if (failedOpMessageID > 0)
466    {
467      buffer.append(", failedOpMessageID=");
468      buffer.append(failedOpMessageID);
469    }
470
471    if (! opResponseControls.isEmpty())
472    {
473      buffer.append(", opResponseControls={");
474
475      for (final int msgID : opResponseControls.keySet())
476      {
477        buffer.append("opMsgID=");
478        buffer.append(msgID);
479        buffer.append(", opControls={");
480
481        boolean first = true;
482        for (final Control c : opResponseControls.get(msgID))
483        {
484          if (first)
485          {
486            first = false;
487          }
488          else
489          {
490            buffer.append(", ");
491          }
492
493          buffer.append(c);
494        }
495        buffer.append('}');
496      }
497
498      buffer.append('}');
499    }
500
501    final String diagnosticMessage = getDiagnosticMessage();
502    if (diagnosticMessage != null)
503    {
504      buffer.append(", diagnosticMessage='");
505      buffer.append(diagnosticMessage);
506      buffer.append('\'');
507    }
508
509    final String matchedDN = getMatchedDN();
510    if (matchedDN != null)
511    {
512      buffer.append(", matchedDN='");
513      buffer.append(matchedDN);
514      buffer.append('\'');
515    }
516
517    final String[] referralURLs = getReferralURLs();
518    if (referralURLs.length > 0)
519    {
520      buffer.append(", referralURLs={");
521      for (int i=0; i < referralURLs.length; i++)
522      {
523        if (i > 0)
524        {
525          buffer.append(", ");
526        }
527
528        buffer.append('\'');
529        buffer.append(referralURLs[i]);
530        buffer.append('\'');
531      }
532      buffer.append('}');
533    }
534
535    final Control[] responseControls = getResponseControls();
536    if (responseControls.length > 0)
537    {
538      buffer.append(", responseControls={");
539      for (int i=0; i < responseControls.length; i++)
540      {
541        if (i > 0)
542        {
543          buffer.append(", ");
544        }
545
546        buffer.append(responseControls[i]);
547      }
548      buffer.append('}');
549    }
550
551    buffer.append(')');
552  }
553}