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 }