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