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 }