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