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