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