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