001/*
002 * Copyright 2007-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
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.NotMutable;
039import com.unboundid.util.ThreadSafety;
040import com.unboundid.util.ThreadSafetyLevel;
041
042import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
043import static com.unboundid.util.Debug.*;
044import static com.unboundid.util.StaticUtils.*;
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 Alcatel-Lucent 8661
057 *   server products.  These classes provide support for proprietary
058 *   functionality or for external specifications that are not considered stable
059 *   or mature enough to be guaranteed to work in an interoperable way with
060 *   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<Integer,Control[]>();
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      debugException(ae);
145      throw new LDAPException(ResultCode.DECODING_ERROR,
146                              ERR_END_TXN_RESPONSE_VALUE_NOT_SEQUENCE.get(
147                                   ae.getMessage()),
148                              ae);
149    }
150
151    final ASN1Element[] valueElements = valueSequence.elements();
152    if (valueElements.length == 0)
153    {
154      failedOpMessageID = -1;
155      return;
156    }
157    else if (valueElements.length > 2)
158    {
159      throw new LDAPException(ResultCode.DECODING_ERROR,
160                              ERR_END_TXN_RESPONSE_INVALID_ELEMENT_COUNT.get(
161                                   valueElements.length));
162    }
163
164    int msgID = -1;
165    for (final ASN1Element e : valueElements)
166    {
167      if (e.getType() == ASN1Constants.UNIVERSAL_INTEGER_TYPE)
168      {
169        try
170        {
171          msgID = ASN1Integer.decodeAsInteger(e).intValue();
172        }
173        catch (final ASN1Exception ae)
174        {
175          debugException(ae);
176          throw new LDAPException(ResultCode.DECODING_ERROR,
177                         ERR_END_TXN_RESPONSE_CANNOT_DECODE_MSGID.get(ae), ae);
178        }
179      }
180      else if (e.getType() == ASN1Constants.UNIVERSAL_SEQUENCE_TYPE)
181      {
182        decodeOpControls(e, opResponseControls);
183      }
184      else
185      {
186        throw new LDAPException(ResultCode.DECODING_ERROR,
187                                ERR_END_TXN_RESPONSE_INVALID_TYPE.get(
188                                     toHex(e.getType())));
189      }
190    }
191
192    failedOpMessageID = msgID;
193  }
194
195
196
197  /**
198   * Creates a new end batched transaction extended result with the provided
199   * information.
200   *
201   * @param  messageID           The message ID for the LDAP message that is
202   *                             associated with this LDAP result.
203   * @param  resultCode          The result code from the response.
204   * @param  diagnosticMessage   The diagnostic message from the response, if
205   *                             available.
206   * @param  matchedDN           The matched DN from the response, if available.
207   * @param  referralURLs        The set of referral URLs from the response, if
208   *                             available.
209   * @param  failedOpMessageID   The message ID for the operation that failed,
210   *                             or {@code null} if there was no failure.
211   * @param  opResponseControls  A map containing the response controls for each
212   *                             operation, indexed by message ID.  It may be
213   *                             {@code null} if there were no response
214   *                             controls.
215   * @param  responseControls    The set of controls from the response, if
216   *                             available.
217   */
218  public EndBatchedTransactionExtendedResult(final int messageID,
219              final ResultCode resultCode, final String diagnosticMessage,
220              final String matchedDN, final String[] referralURLs,
221              final Integer failedOpMessageID,
222              final Map<Integer,Control[]> opResponseControls,
223              final Control[] responseControls)
224  {
225    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
226          null, encodeValue(failedOpMessageID, opResponseControls),
227                            responseControls);
228
229    if ((failedOpMessageID == null) || (failedOpMessageID <= 0))
230    {
231      this.failedOpMessageID = -1;
232    }
233    else
234    {
235      this.failedOpMessageID = failedOpMessageID;
236    }
237
238    if (opResponseControls == null)
239    {
240      this.opResponseControls = new TreeMap<Integer,Control[]>();
241    }
242    else
243    {
244      this.opResponseControls =
245           new TreeMap<Integer,Control[]>(opResponseControls);
246    }
247  }
248
249
250
251  /**
252   * Decodes the provided ASN.1 element as an update controls sequence.  Each
253   * element of the sequence should itself be a sequence containing the message
254   * ID associated with the operation in which the control was returned and a
255   * sequence of the controls included in the response for that operation.
256   *
257   * @param  element     The ASN.1 element to be decoded.
258   * @param  controlMap  The map into which to place the decoded controls.
259   *
260   * @throws  LDAPException  If a problem occurs while attempting to decode the
261   *                         contents of the provided ASN.1 element.
262   */
263  private static void decodeOpControls(final ASN1Element element,
264                                       final Map<Integer,Control[]> controlMap)
265          throws LDAPException
266  {
267    final ASN1Sequence ctlsSequence;
268    try
269    {
270      ctlsSequence = ASN1Sequence.decodeAsSequence(element);
271    }
272    catch (final ASN1Exception ae)
273    {
274      debugException(ae);
275      throw new LDAPException(ResultCode.DECODING_ERROR,
276                     ERR_END_TXN_RESPONSE_CONTROLS_NOT_SEQUENCE.get(ae), ae);
277    }
278
279    for (final ASN1Element e : ctlsSequence.elements())
280    {
281      final ASN1Sequence ctlSequence;
282      try
283      {
284        ctlSequence = ASN1Sequence.decodeAsSequence(e);
285      }
286      catch (final ASN1Exception ae)
287      {
288        debugException(ae);
289        throw new LDAPException(ResultCode.DECODING_ERROR,
290                       ERR_END_TXN_RESPONSE_CONTROL_NOT_SEQUENCE.get(ae), ae);
291      }
292
293      final ASN1Element[] ctlSequenceElements = ctlSequence.elements();
294      if (ctlSequenceElements.length != 2)
295      {
296        throw new LDAPException(ResultCode.DECODING_ERROR,
297                       ERR_END_TXN_RESPONSE_CONTROL_INVALID_ELEMENT_COUNT.get(
298                            ctlSequenceElements.length));
299      }
300
301      final int msgID;
302      try
303      {
304        msgID = ASN1Integer.decodeAsInteger(ctlSequenceElements[0]).intValue();
305      }
306      catch (final ASN1Exception ae)
307      {
308        debugException(ae);
309        throw new LDAPException(ResultCode.DECODING_ERROR,
310                       ERR_END_TXN_RESPONSE_CONTROL_MSGID_NOT_INT.get(ae), ae);
311      }
312
313      final ASN1Sequence controlsSequence;
314      try
315      {
316        controlsSequence =
317             ASN1Sequence.decodeAsSequence(ctlSequenceElements[1]);
318      }
319      catch (final ASN1Exception ae)
320      {
321        debugException(ae);
322        throw new LDAPException(ResultCode.DECODING_ERROR,
323             ERR_END_TXN_RESPONSE_CONTROLS_ELEMENT_NOT_SEQUENCE.get(ae), ae);
324      }
325
326      final Control[] controls = Control.decodeControls(controlsSequence);
327      if (controls.length == 0)
328      {
329        continue;
330      }
331
332      controlMap.put(msgID, controls);
333    }
334  }
335
336
337
338  /**
339   * Encodes the provided information into an appropriate value for this
340   * control.
341   *
342   * @param  failedOpMessageID   The message ID for the operation that failed,
343   *                             or {@code null} if there was no failure.
344   * @param  opResponseControls  A map containing the response controls for each
345   *                             operation, indexed by message ID.  It may be
346   *                             {@code null} if there were no response
347   *                             controls.
348   *
349   * @return  An ASN.1 octet string containing the encoded value for this
350   *          control, or {@code null} if there should not be a value.
351   */
352  private static ASN1OctetString encodeValue(final Integer failedOpMessageID,
353                      final Map<Integer,Control[]> opResponseControls)
354  {
355    if ((failedOpMessageID == null) && (opResponseControls == null))
356    {
357      return null;
358    }
359
360    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2);
361    if (failedOpMessageID != null)
362    {
363      elements.add(new ASN1Integer(failedOpMessageID));
364    }
365
366    if ((opResponseControls != null) && (! opResponseControls.isEmpty()))
367    {
368      final ArrayList<ASN1Element> controlElements =
369           new ArrayList<ASN1Element>();
370      for (final Map.Entry<Integer,Control[]> e : opResponseControls.entrySet())
371      {
372        final ASN1Element[] ctlElements =
373        {
374          new ASN1Integer(e.getKey()),
375          Control.encodeControls(e.getValue())
376        };
377        controlElements.add(new ASN1Sequence(ctlElements));
378      }
379
380      elements.add(new ASN1Sequence(controlElements));
381    }
382
383    return new ASN1OctetString(new ASN1Sequence(elements).encode());
384  }
385
386
387
388
389  /**
390   * Retrieves the message ID of the operation that caused the transaction
391   * processing to fail, if applicable.
392   *
393   * @return  The message ID of the operation that caused the transaction
394   *          processing to fail, or -1 if no message ID was included in the
395   *          end transaction response.
396   */
397  public int getFailedOpMessageID()
398  {
399    return failedOpMessageID;
400  }
401
402
403
404  /**
405   * Retrieves the set of response controls returned by the operations
406   * processed as part of the transaction.  The value returned will contain a
407   * mapping between the message ID of the associated request message and a list
408   * of the response controls for that operation.
409   *
410   * @return  The set of response controls returned by the operations processed
411   *          as part of the transaction.  It may be an empty map if none of the
412   *          operations had any response controls.
413   */
414  public Map<Integer,Control[]> getOperationResponseControls()
415  {
416    return opResponseControls;
417  }
418
419
420
421  /**
422   * Retrieves the set of response controls returned by the specified operation
423   * processed as part of the transaction.
424   *
425   * @param  messageID  The message ID of the operation for which to retrieve
426   *                    the response controls.
427   *
428   * @return  The response controls for the specified operation, or
429   *          {@code null} if there were no controls returned for the specified
430   *          operation.
431   */
432  public Control[] getOperationResponseControls(final int messageID)
433  {
434    return opResponseControls.get(messageID);
435  }
436
437
438
439  /**
440   * {@inheritDoc}
441   */
442  @Override()
443  public String getExtendedResultName()
444  {
445    return INFO_EXTENDED_RESULT_NAME_END_BATCHED_TXN.get();
446  }
447
448
449
450  /**
451   * Appends a string representation of this extended result to the provided
452   * buffer.
453   *
454   * @param  buffer  The buffer to which a string representation of this
455   *                 extended result will be appended.
456   */
457  @Override()
458  public void toString(final StringBuilder buffer)
459  {
460    buffer.append("EndBatchedTransactionExtendedResult(resultCode=");
461    buffer.append(getResultCode());
462
463    final int messageID = getMessageID();
464    if (messageID >= 0)
465    {
466      buffer.append(", messageID=");
467      buffer.append(messageID);
468    }
469
470    if (failedOpMessageID > 0)
471    {
472      buffer.append(", failedOpMessageID=");
473      buffer.append(failedOpMessageID);
474    }
475
476    if (! opResponseControls.isEmpty())
477    {
478      buffer.append(", opResponseControls={");
479
480      for (final int msgID : opResponseControls.keySet())
481      {
482        buffer.append("opMsgID=");
483        buffer.append(msgID);
484        buffer.append(", opControls={");
485
486        boolean first = true;
487        for (final Control c : opResponseControls.get(msgID))
488        {
489          if (first)
490          {
491            first = false;
492          }
493          else
494          {
495            buffer.append(", ");
496          }
497
498          buffer.append(c);
499        }
500        buffer.append('}');
501      }
502
503      buffer.append('}');
504    }
505
506    final String diagnosticMessage = getDiagnosticMessage();
507    if (diagnosticMessage != null)
508    {
509      buffer.append(", diagnosticMessage='");
510      buffer.append(diagnosticMessage);
511      buffer.append('\'');
512    }
513
514    final String matchedDN = getMatchedDN();
515    if (matchedDN != null)
516    {
517      buffer.append(", matchedDN='");
518      buffer.append(matchedDN);
519      buffer.append('\'');
520    }
521
522    final String[] referralURLs = getReferralURLs();
523    if (referralURLs.length > 0)
524    {
525      buffer.append(", referralURLs={");
526      for (int i=0; i < referralURLs.length; i++)
527      {
528        if (i > 0)
529        {
530          buffer.append(", ");
531        }
532
533        buffer.append('\'');
534        buffer.append(referralURLs[i]);
535        buffer.append('\'');
536      }
537      buffer.append('}');
538    }
539
540    final Control[] responseControls = getResponseControls();
541    if (responseControls.length > 0)
542    {
543      buffer.append(", responseControls={");
544      for (int i=0; i < responseControls.length; i++)
545      {
546        if (i > 0)
547        {
548          buffer.append(", ");
549        }
550
551        buffer.append(responseControls[i]);
552      }
553      buffer.append('}');
554    }
555
556    buffer.append(')');
557  }
558}