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