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