001 /*
002 * Copyright 2010-2013 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2010-2013 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 }