001/* 002 * Copyright 2017-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2017-2019 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.controls; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.LinkedHashMap; 028import java.util.Map; 029 030import com.unboundid.asn1.ASN1Boolean; 031import com.unboundid.asn1.ASN1Element; 032import com.unboundid.asn1.ASN1OctetString; 033import com.unboundid.asn1.ASN1Sequence; 034import com.unboundid.ldap.sdk.Control; 035import com.unboundid.ldap.sdk.DecodeableControl; 036import com.unboundid.ldap.sdk.LDAPException; 037import com.unboundid.ldap.sdk.LDAPResult; 038import com.unboundid.ldap.sdk.ResultCode; 039import com.unboundid.util.Debug; 040import com.unboundid.util.NotMutable; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044import com.unboundid.util.Validator; 045 046import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 047 048 049 050/** 051 * This class provides a response control that may be included in the response 052 * to add, modify, and modify DN requests that included the 053 * {@link UniquenessRequestControl}. It provides information about the 054 * uniqueness processing that was performed. 055 * <BR> 056 * <BLOCKQUOTE> 057 * <B>NOTE:</B> This class, and other classes within the 058 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 059 * supported for use against Ping Identity, UnboundID, and 060 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 061 * for proprietary functionality or for external specifications that are not 062 * considered stable or mature enough to be guaranteed to work in an 063 * interoperable way with other types of LDAP servers. 064 * </BLOCKQUOTE> 065 * <BR> 066 * The control has an OID of 1.3.6.1.4.1.30221.2.5.53 and a criticality of 067 * false. It must have a value with the following encoding: 068 * <PRE> 069 * UniquenessResponseValue ::= SEQUENCE { 070 * uniquenessID [0] OCTET STRING, 071 * preCommitValidationPassed [1] BOOLEAN OPTIONAL, 072 * postCommitValidationPassed [2] BOOLEAN OPTIONAL, 073 * validationMessage [3] OCTET STRING OPTIONAL, 074 * ... } 075 * </PRE> 076 */ 077@NotMutable() 078@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 079public final class UniquenessResponseControl 080 extends Control 081 implements DecodeableControl 082{ 083 /** 084 * The OID (1.3.6.1.4.1.30221.2.5.53) for the uniqueness response control. 085 */ 086 public static final String UNIQUENESS_RESPONSE_OID = 087 "1.3.6.1.4.1.30221.2.5.53"; 088 089 090 091 /** 092 * The BER type for the uniqueness ID element in the value sequence. 093 */ 094 private static final byte TYPE_UNIQUENESS_ID = (byte) 0x80; 095 096 097 098 /** 099 * The BER type for the pre-commit validation passed element in the value 100 * sequence. 101 */ 102 private static final byte TYPE_PRE_COMMIT_VALIDATION_PASSED = (byte) 0x81; 103 104 105 106 /** 107 * The BER type for the post-commit validation passed element in the value 108 * sequence. 109 */ 110 private static final byte TYPE_POST_COMMIT_VALIDATION_PASSED = (byte) 0x82; 111 112 113 114 /** 115 * The BER type for the validation message element in the value sequence. 116 */ 117 private static final byte TYPE_VALIDATION_MESSAGE = (byte) 0x83; 118 119 120 121 /** 122 * The serial version UID for this serializable class. 123 */ 124 private static final long serialVersionUID = 5090348902351420617L; 125 126 127 128 // Indicates whether post-commit validation passed. 129 private final Boolean postCommitValidationPassed; 130 131 // Indicates whether pre-commit validation passed. 132 private final Boolean preCommitValidationPassed; 133 134 // A value that will be used to correlate this response control with its 135 // corresponding request control. 136 private final String uniquenessID; 137 138 // The validation message, if any. 139 private final String validationMessage; 140 141 142 143 /** 144 * Creates a new empty control instance that is intended to be used only for 145 * decoding controls via the {@code DecodeableControl} interface. 146 */ 147 UniquenessResponseControl() 148 { 149 uniquenessID = null; 150 preCommitValidationPassed = null; 151 postCommitValidationPassed = null; 152 validationMessage = null; 153 } 154 155 156 157 /** 158 * Creates a new uniqueness response control with the provided information. 159 * 160 * @param uniquenessID The uniqueness ID that may be used to 161 * correlate this uniqueness response 162 * control with the corresponding request 163 * control. This must not be 164 * {@code null}. 165 * @param preCommitValidationPassed Indicates whether the pre-commit 166 * validation was successful. This may be 167 * {@code null} if no pre-commit 168 * validation was attempted. 169 * @param postCommitValidationPassed Indicates whether the post-commit 170 * validation was successful. This may be 171 * {@code null} if no post-commit 172 * validation was attempted. 173 * @param validationMessage A message with additional information 174 * about the validation processing. This 175 * may be {@code null} if no validation 176 * message is needed. 177 */ 178 public UniquenessResponseControl(final String uniquenessID, 179 final Boolean preCommitValidationPassed, 180 final Boolean postCommitValidationPassed, 181 final String validationMessage) 182 { 183 super(UNIQUENESS_RESPONSE_OID, false, 184 encodeValue(uniquenessID, preCommitValidationPassed, 185 postCommitValidationPassed, validationMessage)); 186 187 Validator.ensureNotNull(uniquenessID); 188 189 this.uniquenessID = uniquenessID; 190 this.preCommitValidationPassed = preCommitValidationPassed; 191 this.postCommitValidationPassed = postCommitValidationPassed; 192 this.validationMessage = validationMessage; 193 } 194 195 196 197 /** 198 * Encodes the provided information into an ASN.1 octet string suitable for 199 * use as the value of this control. 200 * 201 * @param uniquenessID The uniqueness ID that may be used to 202 * correlate this uniqueness response 203 * control with the corresponding request 204 * control. This must not be 205 * {@code null}. 206 * @param preCommitValidationPassed Indicates whether the pre-commit 207 * validation was successful. This may be 208 * {@code null} if no pre-commit 209 * validation was attempted. 210 * @param postCommitValidationPassed Indicates whether the post-commit 211 * validation was successful. This may be 212 * {@code null} if no post-commit 213 * validation was attempted. 214 * @param validationMessage A message with additional information 215 * about the validation processing. This 216 * may be {@code null} if no validation 217 * message is needed. 218 * 219 * @return The encoded control value. 220 */ 221 private static ASN1OctetString encodeValue(final String uniquenessID, 222 final Boolean preCommitValidationPassed, 223 final Boolean postCommitValidationPassed, 224 final String validationMessage) 225 { 226 final ArrayList<ASN1Element> elements = new ArrayList<>(4); 227 elements.add(new ASN1OctetString(TYPE_UNIQUENESS_ID, uniquenessID)); 228 229 if (preCommitValidationPassed != null) 230 { 231 elements.add(new ASN1Boolean(TYPE_PRE_COMMIT_VALIDATION_PASSED, 232 preCommitValidationPassed)); 233 } 234 235 if (postCommitValidationPassed != null) 236 { 237 elements.add(new ASN1Boolean(TYPE_POST_COMMIT_VALIDATION_PASSED, 238 postCommitValidationPassed)); 239 } 240 241 if (validationMessage != null) 242 { 243 elements.add(new ASN1OctetString(TYPE_VALIDATION_MESSAGE, 244 validationMessage)); 245 } 246 247 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 248 } 249 250 251 252 /** 253 * Creates a new uniqueness response control with the provided information. 254 * 255 * @param oid The OID for the control. 256 * @param isCritical Indicates whether the control should be marked 257 * critical. 258 * @param value The encoded value for the control. This may be 259 * {@code null} if no value was provided. 260 * 261 * @throws LDAPException If the provided control cannot be decoded as a 262 * uniqueness response control. 263 */ 264 public UniquenessResponseControl(final String oid, final boolean isCritical, 265 final ASN1OctetString value) 266 throws LDAPException 267 { 268 super(oid, isCritical, value); 269 270 if (value == null) 271 { 272 throw new LDAPException(ResultCode.DECODING_ERROR, 273 ERR_UNIQUENESS_RES_DECODE_NO_VALUE.get()); 274 } 275 276 try 277 { 278 String id = null; 279 Boolean prePassed = null; 280 Boolean postPassed = null; 281 String message = null; 282 for (final ASN1Element e : 283 ASN1Sequence.decodeAsSequence(value.getValue()).elements()) 284 { 285 switch (e.getType()) 286 { 287 case TYPE_UNIQUENESS_ID: 288 id = ASN1OctetString.decodeAsOctetString(e).stringValue(); 289 break; 290 case TYPE_PRE_COMMIT_VALIDATION_PASSED: 291 prePassed = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 292 break; 293 case TYPE_POST_COMMIT_VALIDATION_PASSED: 294 postPassed = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 295 break; 296 case TYPE_VALIDATION_MESSAGE: 297 message = ASN1OctetString.decodeAsOctetString(e).stringValue(); 298 break; 299 default: 300 throw new LDAPException(ResultCode.DECODING_ERROR, 301 ERR_UNIQUENESS_RES_DECODE_UNKNOWN_ELEMENT_TYPE.get( 302 StaticUtils.toHex(e.getType()))); 303 } 304 } 305 306 if (id == null) 307 { 308 throw new LDAPException(ResultCode.DECODING_ERROR, 309 ERR_UNIQUENESS_RES_DECODE_NO_UNIQUENESS_ID.get()); 310 } 311 312 uniquenessID = id; 313 preCommitValidationPassed = prePassed; 314 postCommitValidationPassed = postPassed; 315 validationMessage = message; 316 } 317 catch (final LDAPException le) 318 { 319 Debug.debugException(le); 320 throw le; 321 } 322 catch (final Exception e) 323 { 324 Debug.debugException(e); 325 throw new LDAPException(ResultCode.DECODING_ERROR, 326 ERR_UNIQUENESS_RES_DECODE_ERROR.get( 327 StaticUtils.getExceptionMessage(e)), 328 e); 329 } 330 } 331 332 333 334 /** 335 * {@inheritDoc} 336 */ 337 @Override() 338 public UniquenessResponseControl decodeControl(final String oid, 339 final boolean isCritical, 340 final ASN1OctetString value) 341 throws LDAPException 342 { 343 return new UniquenessResponseControl(oid, isCritical, value); 344 } 345 346 347 348 /** 349 * Retrieves the set of uniqueness response controls included in the provided 350 * result. 351 * 352 * @param result The result to process. 353 * 354 * @return The set of uniqueness response controls included in the provided 355 * result, indexed by uniqueness ID. It may be empty if the result 356 * does not include any uniqueness response controls. 357 * 358 * @throws LDAPException If a problem is encountered while getting the set 359 * of uniqueness response controls contained in the 360 * provided result. 361 */ 362 public static Map<String,UniquenessResponseControl> 363 get(final LDAPResult result) 364 throws LDAPException 365 { 366 final Control[] responseControls = result.getResponseControls(); 367 if (responseControls.length == 0) 368 { 369 return Collections.emptyMap(); 370 } 371 372 final LinkedHashMap<String,UniquenessResponseControl> controlMap = 373 new LinkedHashMap<>(StaticUtils.computeMapCapacity( 374 responseControls.length)); 375 for (final Control c : responseControls) 376 { 377 if (! c.getOID().equals(UNIQUENESS_RESPONSE_OID)) 378 { 379 continue; 380 } 381 382 final UniquenessResponseControl urc; 383 if (c instanceof UniquenessResponseControl) 384 { 385 urc = (UniquenessResponseControl) c; 386 } 387 else 388 { 389 urc = new UniquenessResponseControl().decodeControl(c.getOID(), 390 c.isCritical(), c.getValue()); 391 } 392 393 final String uniquenessID = urc.getUniquenessID(); 394 if (controlMap.containsKey(uniquenessID)) 395 { 396 throw new LDAPException(ResultCode.DECODING_ERROR, 397 ERR_UNIQUENESS_RES_GET_ID_CONFLICT.get(uniquenessID)); 398 } 399 else 400 { 401 controlMap.put(uniquenessID, urc); 402 } 403 } 404 405 return Collections.unmodifiableMap(controlMap); 406 } 407 408 409 410 /** 411 * Indicates whether a uniqueness conflict was found during processing. 412 * 413 * @return {@code true} if a uniqueness conflict was found during processing, 414 * or {@code false} if no conflict was found or if no validation was 415 * attempted. 416 */ 417 public boolean uniquenessConflictFound() 418 { 419 return ((preCommitValidationPassed == Boolean.FALSE) || 420 (postCommitValidationPassed == Boolean.FALSE)); 421 } 422 423 424 425 /** 426 * Retrieves the identifier that may be used to correlate this uniqueness 427 * response control with the corresponding request control. This is primarily 428 * useful for requests that contain multiple uniqueness controls, as there may 429 * be a separate response control for each. 430 * 431 * @return The identifier that may be used to correlate this uniqueness 432 * response control with the corresponding request control. 433 */ 434 public String getUniquenessID() 435 { 436 return uniquenessID; 437 } 438 439 440 441 /** 442 * Retrieves the result of the server's pre-commit validation processing. 443 * The same information can be inferred from the 444 * {@link #getPreCommitValidationPassed()} method, but this method may provide 445 * a more intuitive result and does not have the possibility of a {@code null} 446 * return value. 447 * 448 * @return {@link UniquenessValidationResult#VALIDATION_PASSED} if the 449 * server did not find any conflicting entries during the pre-commit 450 * check, {@link UniquenessValidationResult#VALIDATION_FAILED} if 451 * the server found at least one conflicting entry during the 452 * pre-commit check, or 453 * {@link UniquenessValidationResult#VALIDATION_NOT_ATTEMPTED} if 454 * the server did not attempt any pre-commit validation. 455 */ 456 public UniquenessValidationResult getPreCommitValidationResult() 457 { 458 if (preCommitValidationPassed == null) 459 { 460 return UniquenessValidationResult.VALIDATION_NOT_ATTEMPTED; 461 } 462 else if (preCommitValidationPassed) 463 { 464 return UniquenessValidationResult.VALIDATION_PASSED; 465 } 466 else 467 { 468 return UniquenessValidationResult.VALIDATION_FAILED; 469 } 470 } 471 472 473 474 /** 475 * Retrieves a value that indicates whether pre-commit validation was 476 * attempted, and whether that validation passed. Note that this method is 477 * still supported and is not deprecated at this time, but the 478 * {@link #getPreCommitValidationResult()} is now the recommended way to get 479 * this information. 480 * 481 * @return {@code Boolean.TRUE} if pre-commit validation was attempted and 482 * passed, {@code Boolean.FALSE} if pre-commit validation was 483 * attempted and did not pass, or {@code null} if pre-commit 484 * validation was not attempted. 485 */ 486 public Boolean getPreCommitValidationPassed() 487 { 488 return preCommitValidationPassed; 489 } 490 491 492 493 /** 494 * Retrieves the result of the server's post-commit validation processing. 495 * The same information can be inferred from the 496 * {@link #getPostCommitValidationPassed()} method, but this method may 497 * provide a more intuitive result and does not have the possibility of a 498 * {@code null} return value. 499 * 500 * @return {@link UniquenessValidationResult#VALIDATION_PASSED} if the 501 * server did not find any conflicting entries during the post-commit 502 * check, {@link UniquenessValidationResult#VALIDATION_FAILED} if 503 * the server found at least one conflicting entry during the 504 * post-commit check, or 505 * {@link UniquenessValidationResult#VALIDATION_NOT_ATTEMPTED} if 506 * the server did not attempt any post-commit validation. 507 */ 508 public UniquenessValidationResult getPostCommitValidationResult() 509 { 510 if (postCommitValidationPassed == null) 511 { 512 return UniquenessValidationResult.VALIDATION_NOT_ATTEMPTED; 513 } 514 else if (postCommitValidationPassed) 515 { 516 return UniquenessValidationResult.VALIDATION_PASSED; 517 } 518 else 519 { 520 return UniquenessValidationResult.VALIDATION_FAILED; 521 } 522 } 523 524 525 526 /** 527 * Retrieves a value that indicates whether post-commit validation was 528 * attempted, and whether that validation passed. 529 * 530 * @return {@code Boolean.TRUE} if post-commit validation was attempted and 531 * passed, {@code Boolean.FALSE} if post-commit validation was 532 * attempted and did not pass, or {@code null} if post-commit 533 * validation was not attempted. 534 */ 535 public Boolean getPostCommitValidationPassed() 536 { 537 return postCommitValidationPassed; 538 } 539 540 541 542 /** 543 * Retrieves a message with additional information about the validation 544 * processing that was performed. 545 * 546 * @return A message with additional information about the validation 547 * processing that was performed, or {@code null} if no validation 548 * message is available. 549 */ 550 public String getValidationMessage() 551 { 552 return validationMessage; 553 } 554 555 556 557 /** 558 * {@inheritDoc} 559 */ 560 @Override() 561 public String getControlName() 562 { 563 return INFO_UNIQUENESS_RES_CONTROL_NAME.get(); 564 } 565 566 567 568 /** 569 * {@inheritDoc} 570 */ 571 @Override() 572 public void toString(final StringBuilder buffer) 573 { 574 buffer.append("UniquenessResponseControl(uniquenessID='"); 575 buffer.append(uniquenessID); 576 buffer.append("', preCommitValidationResult='"); 577 buffer.append(getPreCommitValidationResult().getName()); 578 buffer.append("', preCommitValidationResult='"); 579 buffer.append(getPostCommitValidationResult().getName()); 580 buffer.append('\''); 581 582 if (validationMessage != null) 583 { 584 buffer.append(", validationMessage='"); 585 buffer.append(validationMessage); 586 buffer.append('\''); 587 } 588 buffer.append(')'); 589 } 590}