001    /*
002     * Copyright 2010-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2010-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.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.extensions.ExtOpMessages.*;
043    import static com.unboundid.util.Debug.*;
044    import 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)
060    public 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    }