001/* 002 * Copyright 2017-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2017-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) 2017-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.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.Iterator; 043import java.util.LinkedHashMap; 044import java.util.LinkedHashSet; 045import java.util.List; 046import java.util.Map; 047import java.util.Set; 048 049import com.unboundid.asn1.ASN1Boolean; 050import com.unboundid.asn1.ASN1Element; 051import com.unboundid.asn1.ASN1Enumerated; 052import com.unboundid.asn1.ASN1OctetString; 053import com.unboundid.asn1.ASN1Sequence; 054import com.unboundid.asn1.ASN1Set; 055import com.unboundid.ldap.sdk.Control; 056import com.unboundid.ldap.sdk.Filter; 057import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 058import com.unboundid.ldap.sdk.LDAPException; 059import com.unboundid.ldap.sdk.ResultCode; 060import com.unboundid.util.CryptoHelper; 061import com.unboundid.util.Debug; 062import com.unboundid.util.NotMutable; 063import com.unboundid.util.NotNull; 064import com.unboundid.util.Nullable; 065import com.unboundid.util.StaticUtils; 066import com.unboundid.util.ThreadSafety; 067import com.unboundid.util.ThreadSafetyLevel; 068import com.unboundid.util.Validator; 069import com.unboundid.util.json.JSONArray; 070import com.unboundid.util.json.JSONBoolean; 071import com.unboundid.util.json.JSONField; 072import com.unboundid.util.json.JSONObject; 073import com.unboundid.util.json.JSONString; 074import com.unboundid.util.json.JSONValue; 075 076import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 077 078 079 080/** 081 * This class provides a request control that may be included in an add, modify, 082 * or modify DN request to ensure that the contents of that request will not 083 * result in a uniqueness conflict with any other entry in the server. Each 084 * instance of this control should define exactly one uniqueness constraint for 085 * the associated operation. Multiple instances of this control can be included 086 * in the same request to define multiple independent uniqueness constraints 087 * that must all be satisfied. If any of the uniqueness constraints is not 088 * satisfied, then the corresponding LDAP result should have a result code of 089 * {@link ResultCode#ASSERTION_FAILED} and a {@link UniquenessResponseControl} 090 * for each uniqueness constraint that was not satisfied. 091 * <BR> 092 * <BLOCKQUOTE> 093 * <B>NOTE:</B> This class, and other classes within the 094 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 095 * supported for use against Ping Identity, UnboundID, and 096 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 097 * for proprietary functionality or for external specifications that are not 098 * considered stable or mature enough to be guaranteed to work in an 099 * interoperable way with other types of LDAP servers. 100 * </BLOCKQUOTE> 101 * <BR> 102 * The request properties must contain either one or more attribute types, a 103 * filter, or both. If only a filter is specified, then the server will use 104 * that filter to identify conflicts (for an add request, any matches at all 105 * will be considered a conflict; for a modify or modify DN request, any matches 106 * with any entry other than the one being updated will be considered a 107 * conflict). If a single attribute type is specified with no filter, then any 108 * change that would result in multiple entries having the same value for that 109 * attribute will be considered a conflict. If multiple attribute types are 110 * specified, then the multiple attribute behavior will be used to determine how 111 * to identify conflicts, as documented in the 112 * {@link UniquenessMultipleAttributeBehavior} enum. If both a set of attribute 113 * types and a filter are provided, then only entries matching both sets of 114 * criteria will be considered a conflict. 115 * <BR><BR> 116 * The server can perform two different searches in an attempt to identify 117 * conflicts. In the pre-commit phase, it will attempt to identify any 118 * conflicts that already exist, and will reject the associated change if there 119 * are any. In the post-commit phase, it can see if there were any conflicts 120 * introduced by the change itself or by another change happening at the same 121 * time. If a conflict is detected in the post-commit phase, then the server 122 * won't have prevented it, but at least the control can be used to provide 123 * notification about it. The server may also raise an administrative alert to 124 * notify administrators about the conflict. 125 * <BR><BR> 126 * Although post-commit validation on its own should be able to detect conflicts 127 * that arise as a result of concurrent changes in other instances, it is also 128 * possible to take additional measures to help prevent conflicts from arising 129 * in the first place. The control may indicate that the server should create 130 * a temporary conflict prevention details entry before beginning pre-commit 131 * validation processing. This entry may be found during pre-commit validation 132 * performed for any conflicting concurrent updates so that the conflicting 133 * operation is rejected. This temporary entry will be automatically removed 134 * after uniqueness processing has completed, regardless of its success or 135 * failure. 136 * <BR><BR> 137 * This request control may be sent either directly to a Directory Server 138 * instance, or it may be sent to a Directory Proxy Server with or without entry 139 * balancing. If the request is sent directly to a Directory Server, then only 140 * that one server will be checked for uniqueness conflicts, and it is possible 141 * that concurrent conflicts may be introduced on other servers that have not 142 * yet been replicated by the time control processing has completed. If the 143 * request is sent to a Directory Proxy Server instance, then search may be 144 * processed in one or more backend servers based on the pre-commit and 145 * post-commit validation levels, and at the most paranoid levels, it is highly 146 * unlikely that any conflicts will go unnoticed. 147 * <BR><BR> 148 * The request control has an OID of 1.3.6.1.4.1.30221.2.5.52, a criticality of 149 * either {@code true} or {@code false}, and a value with the following 150 * encoding: 151 * <PRE> 152 * UniquenessRequestValue ::= SEQUENCE { 153 * uniquenessID [0] OCTET STRING, 154 * attributeTypes [1] SET OF OCTET STRING OPTIONAL, 155 * multipleAttributeBehavior [2] ENUMERATED { 156 * uniqueWithinEachAttribute (0), 157 * uniqueAcrossAllAttributesIncludingInSameEntry (1), 158 * uniqueAcrossAllAttributesExceptInSameEntry (2), 159 * uniqueInCombination (3), 160 * ... } DEFAULT uniqueWithinEachAttribute, 161 * baseDN [3] LDAPDN OPTIONAL, 162 * filter [4] Filter OPTIONAL, 163 * preventConflictsWithSoftDeletedEntries [5] BOOLEAN DEFAULT FALSE, 164 * preCommitValidationLevel [6] ENUMERATED { 165 * none (0), 166 * allSubtreeViews (1), 167 * allBackendSets (2), 168 * allAvailableBackendServers (3), 169 * ... } DEFAULT allSubtreeViews, 170 * postCommitValidationLevel [7] ENUMERATED { 171 * none (0), 172 * allSubtreeViews (1), 173 * allBackendSets (2), 174 * allAvailableBackendServers (3), 175 * ... } DEFAULT allSubtreeViews, 176 * alertOnPostCommitConflictDetection [8] BOOLEAN DEFAULT TRUE, 177 * createConflictPreventionDetailsEntry [9] BOOLEAN DEFAULT FALSE, 178 * ... } 179 * </PRE> 180 * <BR><BR> 181 * <H2>Example</H2> 182 * The following example demonstrates how to use the uniqueness request control 183 * to only process an add operation if it does not result in multiple entries 184 * that have the same uid value: 185 * <BR><BR> 186 * <PRE> 187 * // Create the properties to build a uniqueness request control that 188 * // will try to prevent an add operation from creating a new entry 189 * // that has the same uid as an existing entry in the server. During 190 * // pre-commit processing (which happens before the server actually 191 * // processes the add), the server will check at least one server in 192 * // each entry-balancing backend set (or just one server in a 193 * // non-entry-balanced deployment). During post-commit processing 194 * // (which happens if the add succeeds), the server will double-check 195 * // that no conflicting entry was added on any available server in the 196 * // topology. Also ensure that the server will not allow conflicts 197 * // with soft-deleted entries. 198 * final UniquenessRequestControlProperties uniquenessProperties = 199 * new UniquenessRequestControlProperties("uid"); 200 * uniquenessProperties.setPreCommitValidationLevel( 201 * UniquenessValidationLevel.ALL_BACKEND_SETS); 202 * uniquenessProperties.setPostCommitValidationLevel( 203 * UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS); 204 * uniquenessProperties.setPreventConflictsWithSoftDeletedEntries(true); 205 * 206 * // Create the request control. It will be critical so that the 207 * // server will not attempt to process the add if it can't honor the 208 * // uniqueness request. 209 * final boolean isCritical = true; 210 * final String uniquenessID = "uid-uniqueness"; 211 * final UniquenessRequestControl uniquenessRequestControl = 212 * new UniquenessRequestControl(isCritical, uniquenessID, 213 * uniquenessProperties); 214 * 215 * // Attach the control to an add request. 216 * addRequest.addControl(uniquenessRequestControl); 217 * 218 * // Send the add request to the server and read the result. 219 * try 220 * { 221 * final LDAPResult addResult = connection.add(addRequest); 222 * 223 * // The add operation succeeded, so the entry should have been 224 * // created, but there is still the possibility that a post-commit 225 * // conflict was discovered, indicating that another request 226 * // processed at about the same time as our add introduced a 227 * // conflicting entry. 228 * final Map<String,UniquenessResponseControl> uniquenessResponses; 229 * try 230 * { 231 * uniquenessResponses = UniquenessResponseControl.get(addResult); 232 * } 233 * catch (final LDAPException e) 234 * { 235 * throw new RuntimeException( 236 * "The add succeeded, but an error occurred while trying " + 237 * "to decode a uniqueness response control in add " + 238 * "result " + addResult + ": " + 239 * StaticUtils.getExceptionMessage(e), 240 * e); 241 * } 242 * 243 * final UniquenessResponseControl uniquenessResponseControl = 244 * uniquenessResponses.get(uniquenessID); 245 * if ((uniquenessResponseControl != null) && 246 * uniquenessResponseControl.uniquenessConflictFound()) 247 * { 248 * throw new RuntimeException( 249 * "The add succeeded, but a uniqueness conflict was found " + 250 * "Uniqueness validation message: " + 251 * uniquenessResponseControl.getValidationMessage()); 252 * } 253 * } 254 * catch (final LDAPException e) 255 * { 256 * // The add attempt failed. It might have been because of a 257 * // uniqueness problem, or it could have been for some other reason. 258 * // To figure out which it was, look to see if there is an 259 * // appropriate uniqueness response control. 260 * final Map<String, UniquenessResponseControl> uniquenessResponses; 261 * try 262 * { 263 * uniquenessResponses = 264 * UniquenessResponseControl.get(e.toLDAPResult()); 265 * } 266 * catch (final LDAPException e2) 267 * { 268 * throw new LDAPException(e.getResultCode(), 269 * "The add attempt failed with result " + e.toLDAPResult() + 270 * ", and an error occurred while trying to decode a " + 271 * "uniqueness response control in the result: " + 272 * StaticUtils.getExceptionMessage(e2), 273 * e); 274 * } 275 * 276 * final UniquenessResponseControl uniquenessResponseControl = 277 * uniquenessResponses.get(uniquenessID); 278 * if (uniquenessResponseControl == null) 279 * { 280 * // The add result didn't include a uniqueness response control, 281 * // indicating that the failure was not because of a uniqueness 282 * // conflict. 283 * throw e; 284 * } 285 * 286 * if (uniquenessResponseControl.uniquenessConflictFound()) 287 * { 288 * // The add failed, and the uniqueness response control indicates 289 * // that the failure was because of a uniqueness conflict. 290 * 291 * final UniquenessValidationResult preCommitResult = 292 * uniquenessResponseControl.getPreCommitValidationResult(); 293 * final UniquenessValidationResult postCommitResult = 294 * uniquenessResponseControl.getPreCommitValidationResult(); 295 * final String validationMessage = 296 * uniquenessResponseControl.getValidationMessage(); 297 * 298 * throw e; 299 * } 300 * else 301 * { 302 * // The add failed, but the uniqueness response control indicates 303 * // that the failure was not because of a uniqueness conflict. 304 * throw e; 305 * } 306 * } 307 * </PRE> 308 */ 309@NotMutable() 310@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 311public final class UniquenessRequestControl 312 extends Control 313{ 314 /** 315 * The OID (1.3.6.1.4.1.30221.2.5.52) for the uniqueness request control. 316 */ 317 @NotNull public static final String UNIQUENESS_REQUEST_OID = 318 "1.3.6.1.4.1.30221.2.5.52"; 319 320 321 322 /** 323 * The BER type for the uniqueness ID element in the value sequence. 324 */ 325 private static final byte TYPE_UNIQUENESS_ID = (byte) 0x80; 326 327 328 329 /** 330 * The BER type for the attribute types element in the value sequence. 331 */ 332 private static final byte TYPE_ATTRIBUTE_TYPES = (byte) 0xA1; 333 334 335 336 /** 337 * The BER type for the multiple attribute behavior element in the value 338 * sequence. 339 */ 340 private static final byte TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR = (byte) 0x82; 341 342 343 344 /** 345 * The BER type for the base DN element in the value sequence. 346 */ 347 private static final byte TYPE_BASE_DN = (byte) 0x83; 348 349 350 351 /** 352 * The BER type for the filter element in the value sequence. 353 */ 354 private static final byte TYPE_FILTER = (byte) 0xA4; 355 356 357 358 /** 359 * The BER type for the prevent conflicts with soft-deleted entries element in 360 * the value sequence. 361 */ 362 private static final byte TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES = 363 (byte) 0x85; 364 365 366 367 /** 368 * The BER type for the pre-commit validation element in the value sequence. 369 */ 370 private static final byte TYPE_PRE_COMMIT_VALIDATION_LEVEL = (byte) 0x86; 371 372 373 374 /** 375 * The BER type for the post-commit validation element in the value sequence. 376 */ 377 private static final byte TYPE_POST_COMMIT_VALIDATION_LEVEL = (byte) 0x87; 378 379 380 381 /** 382 * The BER type for the value sequence element that indicates whether to 383 * raise an administrative alert if a conflict is detected during post-commit 384 * validation. 385 */ 386 private static final byte TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION = 387 (byte) 0x88; 388 389 390 391 /** 392 * The BER type for the value sequence element that indicates whether to 393 * create a conflict prevention details entry before pre-commit validation as 394 * a means of helping to avoid conflicts. 395 */ 396 private static final byte TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY = 397 (byte) 0x89; 398 399 400 401 /** 402 * The name of the field used to hold the 403 * alert-on-post-commit-conflict-detection flag in the JSON representation of 404 * this control. 405 */ 406 @NotNull private static final String 407 JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION = 408 "alert-on-post-commit-conflict-detection"; 409 410 411 412 /** 413 * The name of the field used to hold the attribute types in the JSON 414 * representation of this control. 415 */ 416 @NotNull private static final String JSON_FIELD_ATTRIBUTE_TYPES = 417 "attribute-types"; 418 419 420 421 /** 422 * The name of the field used to hold the base DN in the JSON representation 423 * of this control. 424 */ 425 @NotNull private static final String JSON_FIELD_BASE_DN = "base-dn"; 426 427 428 429 /** 430 * The name of the field used to hold the 431 * create-conflict-prevention-details-entry flag in the JSON representation of 432 * this control. 433 */ 434 @NotNull private static final String 435 JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY = 436 "create-conflict-prevention-details-entry"; 437 438 439 440 /** 441 * The name of the field used to hold the filter in the JSON representation of 442 * this control. 443 */ 444 @NotNull private static final String JSON_FIELD_FILTER = "filter"; 445 446 447 448 /** 449 * The name of the field used to hold the multiple attribute behavior in the 450 * JSON representation of this control. 451 */ 452 @NotNull private static final String JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR = 453 "multiple-attribute-behavior"; 454 455 456 457 /** 458 * The name of the field used to hold the pre-commit validation level in the 459 * JSON representation of this control. 460 */ 461 @NotNull private static final String JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL = 462 "pre-commit-validation-level"; 463 464 465 466 /** 467 * The name of the field used to hold the post-commit validation level in the 468 * JSON representation of this control. 469 */ 470 @NotNull private static final String 471 JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL = "post-commit-validation-level"; 472 473 474 475 /** 476 * The name of the field used to hold the 477 * prevent-conflicts-with-soft-deleted-entries flag in the JSON representation 478 * of this control. 479 */ 480 @NotNull private static final String 481 JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES = 482 "prevent-conflicts-with-soft-deleted-entries"; 483 484 485 486 /** 487 * The name of the field used to hold the uniqueness ID in the JSON 488 * representation of this control. 489 */ 490 @NotNull private static final String JSON_FIELD_UNIQUENESS_ID = 491 "uniqueness-id"; 492 493 494 495 /** 496 * The unique-within-each-attribute multiple-attribute-behavior value to use 497 * in the JSON representation of this control. 498 */ 499 @NotNull private static final String JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE = 500 "unique-within-each-attribute"; 501 502 503 504 /** 505 * The unique-across-all-attributes-including-in-the-same-entry 506 * multiple-attribute-behavior value to use in the JSON representation of this 507 * control. 508 */ 509 @NotNull private static final String 510 JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY = 511 "unique-across-all-attributes-including-in-the-same-entry"; 512 513 514 515 /** 516 * The unique-across-all-attributes-except-in-the-same-entry 517 * multiple-attribute-behavior value to use in the JSON representation of this 518 * control. 519 */ 520 @NotNull private static final String 521 JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY = 522 "unique-across-all-attributes-except-in-the-same-entry"; 523 524 525 526 /** 527 * The unique-in-combination multiple-attribute-behavior value to use in the 528 * JSON representation of this control. 529 */ 530 @NotNull private static final String JSON_MAB_UNIQUE_IN_COMBINATION = 531 "unique-in-combination"; 532 533 534 535 /** 536 * The none validation level value to use in the JSON representation of this 537 * control. 538 */ 539 @NotNull private static final String JSON_VALIDATION_LEVEL_NONE = "none"; 540 541 542 543 /** 544 * The all-subtree-views validation level value to use in the JSON 545 * representation of this control. 546 */ 547 @NotNull private static final String JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS = 548 "all-subtree-views"; 549 550 551 552 /** 553 * The all-backend-sets validation level value to use in the JSON 554 * representation of this control. 555 */ 556 @NotNull private static final String JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS = 557 "all-backend-sets"; 558 559 560 561 /** 562 * The all-available-backend-servers validation level value to use in the JSON 563 * representation of this control. 564 */ 565 @NotNull private static final String 566 JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS = 567 "all-available-backend-servers"; 568 569 570 571 /** 572 * The serial version UID for this serializable class. 573 */ 574 private static final long serialVersionUID = 7976218379635922852L; 575 576 577 578 // Indicates whether the server should raise an administrative alert if a 579 // conflict is detected during post-commit validation. 580 private final boolean alertOnPostCommitConflictDetection; 581 582 // Indicates whether the server should create a conflict prevention details 583 // entry before pre-commit validation as a means of helping to avoid 584 // conflicts. 585 private final boolean createConflictPreventionDetailsEntry; 586 587 // Indicates whether to prevent conflicts with soft-deleted entries. 588 private final boolean preventConflictsWithSoftDeletedEntries; 589 590 // An optional filter that should be used in the course of identifying 591 // uniqueness conflicts. 592 @Nullable private final Filter filter; 593 594 // A potentially-empty set of attribute types that should be checked for 595 // uniqueness conflicts. 596 @NotNull private final Set<String> attributeTypes; 597 598 // An optional base DN to use when checking for conflicts. 599 @Nullable private final String baseDN; 600 601 // A value that will be used to correlate this request control with its 602 // corresponding response control. 603 @NotNull private final String uniquenessID; 604 605 // The behavior that the server should exhibit if multiple attribute types 606 // are configured. 607 @NotNull private final UniquenessMultipleAttributeBehavior 608 multipleAttributeBehavior; 609 610 // The level of validation that the server should perform before processing 611 // the associated change. 612 @NotNull private final UniquenessValidationLevel postCommitValidationLevel; 613 614 // The level of validation that the server should perform after processing the 615 // associated change. 616 @NotNull private final UniquenessValidationLevel preCommitValidationLevel; 617 618 619 620 /** 621 * Creates a new uniqueness request control with the provided information. 622 * 623 * @param isCritical Indicates whether the control should be considered 624 * critical. 625 * @param uniquenessID A value that will be used to correlate this request 626 * control with its corresponding response control. If 627 * this is {@code null}, then a unique identifier will 628 * be automatically generated. 629 * @param properties The set of properties for this control. It must not 630 * be {@code null}. 631 * 632 * @throws LDAPException If the provided properties cannot be used to create 633 * a valid uniqueness request control. 634 */ 635 public UniquenessRequestControl(final boolean isCritical, 636 @Nullable final String uniquenessID, 637 @NotNull final UniquenessRequestControlProperties properties) 638 throws LDAPException 639 { 640 this((uniquenessID == null 641 ? CryptoHelper.getRandomUUID().toString() 642 : uniquenessID), 643 properties, isCritical); 644 } 645 646 647 648 /** 649 * Creates a new uniqueness request control with the provided information. 650 * Note that this version of the constructor takes the same set of arguments 651 * as the above constructor, but in a different order (to distinguish between 652 * the two versions), and with the additional constraint that the uniqueness 653 * ID must not be {@code null}. 654 * 655 * @param uniquenessID A value that will be used to correlate this request 656 * control with its corresponding response control. It 657 * must not be {@code null}. 658 * @param properties The set of properties for this control. It must not 659 * be {@code null}. 660 * @param isCritical Indicates whether the control should be considered 661 * critical. 662 * 663 * @throws LDAPException If the provided properties cannot be used to create 664 * a valid uniqueness request control. 665 */ 666 private UniquenessRequestControl(@NotNull final String uniquenessID, 667 @NotNull final UniquenessRequestControlProperties properties, 668 final boolean isCritical) 669 throws LDAPException 670 { 671 super(UNIQUENESS_REQUEST_OID, isCritical, 672 encodeValue(uniquenessID, properties)); 673 674 Validator.ensureNotNull(uniquenessID); 675 this.uniquenessID = uniquenessID; 676 677 attributeTypes = properties.getAttributeTypes(); 678 multipleAttributeBehavior = properties.getMultipleAttributeBehavior(); 679 baseDN = properties.getBaseDN(); 680 filter = properties.getFilter(); 681 preventConflictsWithSoftDeletedEntries = 682 properties.preventConflictsWithSoftDeletedEntries(); 683 preCommitValidationLevel = properties.getPreCommitValidationLevel(); 684 postCommitValidationLevel = properties.getPostCommitValidationLevel(); 685 alertOnPostCommitConflictDetection = 686 properties.alertOnPostCommitConflictDetection(); 687 createConflictPreventionDetailsEntry = 688 properties.createConflictPreventionDetailsEntry(); 689 690 if (attributeTypes.isEmpty() && (filter == null)) 691 { 692 throw new LDAPException(ResultCode.PARAM_ERROR, 693 ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get()); 694 } 695 } 696 697 698 699 /** 700 * Encodes the provided information into an octet string that is suitable for 701 * use as the value of this control. 702 * 703 * @param uniquenessID A value that will be used to correlate this request 704 * control with its corresponding response control. It 705 * must not be {@code null}. 706 * @param properties The set of properties for this control. It must not 707 * be {@code null}. 708 * 709 * @return The encoded value that was created. 710 */ 711 @NotNull() 712 private static ASN1OctetString encodeValue(@NotNull final String uniquenessID, 713 @NotNull final UniquenessRequestControlProperties properties) 714 { 715 final ArrayList<ASN1Element> elements = new ArrayList<>(10); 716 717 elements.add(new ASN1OctetString(TYPE_UNIQUENESS_ID, uniquenessID)); 718 719 final Set<String> attributeTypes = properties.getAttributeTypes(); 720 if (!attributeTypes.isEmpty()) 721 { 722 final ArrayList<ASN1Element> attributeTypeElements = 723 new ArrayList<>(attributeTypes.size()); 724 for (final String attributeType : attributeTypes) 725 { 726 attributeTypeElements.add(new ASN1OctetString(attributeType)); 727 } 728 elements.add(new ASN1Set(TYPE_ATTRIBUTE_TYPES, attributeTypeElements)); 729 } 730 731 final UniquenessMultipleAttributeBehavior multipleAttributeBehavior = 732 properties.getMultipleAttributeBehavior(); 733 if (multipleAttributeBehavior != 734 UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE) 735 { 736 elements.add(new ASN1Enumerated(TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR, 737 multipleAttributeBehavior.intValue())); 738 } 739 740 final String baseDN = properties.getBaseDN(); 741 if (baseDN != null) 742 { 743 elements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN)); 744 } 745 746 final Filter filter = properties.getFilter(); 747 if (filter != null) 748 { 749 elements.add(new ASN1Element(TYPE_FILTER, filter.encode().encode())); 750 } 751 752 if (properties.preventConflictsWithSoftDeletedEntries()) 753 { 754 elements.add(new ASN1Boolean( 755 TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES, true)); 756 } 757 758 final UniquenessValidationLevel preCommitValidationLevel = 759 properties.getPreCommitValidationLevel(); 760 if (preCommitValidationLevel != UniquenessValidationLevel.ALL_SUBTREE_VIEWS) 761 { 762 elements.add(new ASN1Enumerated(TYPE_PRE_COMMIT_VALIDATION_LEVEL, 763 preCommitValidationLevel.intValue())); 764 } 765 766 final UniquenessValidationLevel postCommitValidationLevel = 767 properties.getPostCommitValidationLevel(); 768 if (postCommitValidationLevel != 769 UniquenessValidationLevel.ALL_SUBTREE_VIEWS) 770 { 771 elements.add(new ASN1Enumerated(TYPE_POST_COMMIT_VALIDATION_LEVEL, 772 postCommitValidationLevel.intValue())); 773 } 774 775 if (! properties.alertOnPostCommitConflictDetection()) 776 { 777 elements.add(new ASN1Boolean( 778 TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION, false)); 779 } 780 781 if (properties.createConflictPreventionDetailsEntry()) 782 { 783 elements.add(new ASN1Boolean( 784 TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY, true)); 785 } 786 787 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 788 } 789 790 791 792 /** 793 * Creates a new uniqueness request control that is decoded from the provided 794 * generic control. 795 * 796 * @param control The control to be decoded as a uniqueness request control. 797 * It must not be {@code null}. 798 * 799 * @throws LDAPException If the provided control cannot be decoded as a 800 * valid uniqueness request control. 801 */ 802 public UniquenessRequestControl(@NotNull final Control control) 803 throws LDAPException 804 { 805 super(control); 806 807 final ASN1OctetString value = control.getValue(); 808 if (value == null) 809 { 810 throw new LDAPException(ResultCode.DECODING_ERROR, 811 ERR_UNIQUENESS_REQ_DECODE_NO_VALUE.get()); 812 } 813 814 try 815 { 816 boolean decodedAlertOnPostCommitConflictDetection = true; 817 boolean decodedCreateConflictPreventionDetailsEntry = false; 818 boolean decodedPreventSoftDeletedConflicts = false; 819 Filter decodedFilter = null; 820 Set<String> decodedAttributeTypes = Collections.emptySet(); 821 String decodedBaseDN = null; 822 String decodedUniquenessID = null; 823 UniquenessMultipleAttributeBehavior decodedMultipleAttributeBehavior = 824 UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE; 825 UniquenessValidationLevel decodedPreCommitLevel = 826 UniquenessValidationLevel.ALL_SUBTREE_VIEWS; 827 UniquenessValidationLevel decodedPostCommitLevel = 828 UniquenessValidationLevel.ALL_SUBTREE_VIEWS; 829 830 final ASN1Element[] elements = 831 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 832 for (final ASN1Element e : elements) 833 { 834 switch (e.getType()) 835 { 836 case TYPE_UNIQUENESS_ID: 837 decodedUniquenessID = 838 ASN1OctetString.decodeAsOctetString(e).stringValue(); 839 break; 840 case TYPE_ATTRIBUTE_TYPES: 841 final ASN1Element[] atElements = ASN1Set.decodeAsSet(e).elements(); 842 final LinkedHashSet<String> atNames = new LinkedHashSet<>( 843 StaticUtils.computeMapCapacity(atElements.length)); 844 for (final ASN1Element atElement : atElements) 845 { 846 atNames.add(ASN1OctetString.decodeAsOctetString( 847 atElement).stringValue()); 848 } 849 decodedAttributeTypes = Collections.unmodifiableSet(atNames); 850 break; 851 case TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR: 852 final int mabIntValue = 853 ASN1Enumerated.decodeAsEnumerated(e).intValue(); 854 decodedMultipleAttributeBehavior = 855 UniquenessMultipleAttributeBehavior.valueOf(mabIntValue); 856 if (decodedMultipleAttributeBehavior == null) 857 { 858 throw new LDAPException(ResultCode.DECODING_ERROR, 859 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_MULTIPLE_ATTR_BEHAVIOR.get( 860 mabIntValue)); 861 } 862 break; 863 case TYPE_BASE_DN: 864 decodedBaseDN = 865 ASN1OctetString.decodeAsOctetString(e).stringValue(); 866 break; 867 case TYPE_FILTER: 868 decodedFilter = Filter.decode(ASN1Element.decode(e.getValue())); 869 break; 870 case TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES: 871 decodedPreventSoftDeletedConflicts = 872 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 873 break; 874 case TYPE_PRE_COMMIT_VALIDATION_LEVEL: 875 final int preCommitIntValue = 876 ASN1Enumerated.decodeAsEnumerated(e).intValue(); 877 decodedPreCommitLevel = 878 UniquenessValidationLevel.valueOf(preCommitIntValue); 879 if (decodedPreCommitLevel == null) 880 { 881 throw new LDAPException(ResultCode.DECODING_ERROR, 882 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_PRE_COMMIT_LEVEL.get( 883 preCommitIntValue)); 884 } 885 break; 886 case TYPE_POST_COMMIT_VALIDATION_LEVEL: 887 final int postCommitIntValue = 888 ASN1Enumerated.decodeAsEnumerated(e).intValue(); 889 decodedPostCommitLevel = 890 UniquenessValidationLevel.valueOf(postCommitIntValue); 891 if (decodedPostCommitLevel == null) 892 { 893 throw new LDAPException(ResultCode.DECODING_ERROR, 894 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_POST_COMMIT_LEVEL.get( 895 postCommitIntValue)); 896 } 897 break; 898 case TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION: 899 decodedAlertOnPostCommitConflictDetection = 900 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 901 break; 902 case TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY: 903 decodedCreateConflictPreventionDetailsEntry = 904 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 905 break; 906 default: 907 throw new LDAPException(ResultCode.DECODING_ERROR, 908 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_ELEMENT_TYPE.get( 909 StaticUtils.toHex(e.getType()))); 910 } 911 } 912 913 if (decodedUniquenessID == null) 914 { 915 throw new LDAPException(ResultCode.DECODING_ERROR, 916 ERR_UNIQUENESS_REQ_MISSING_UNIQUENESS_ID.get()); 917 } 918 919 if (decodedAttributeTypes.isEmpty() && (decodedFilter == null)) 920 { 921 throw new LDAPException(ResultCode.DECODING_ERROR, 922 ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get()); 923 } 924 925 uniquenessID = decodedUniquenessID; 926 attributeTypes = decodedAttributeTypes; 927 multipleAttributeBehavior = decodedMultipleAttributeBehavior; 928 baseDN = decodedBaseDN; 929 filter = decodedFilter; 930 preventConflictsWithSoftDeletedEntries = 931 decodedPreventSoftDeletedConflicts; 932 preCommitValidationLevel = decodedPreCommitLevel; 933 postCommitValidationLevel = decodedPostCommitLevel; 934 alertOnPostCommitConflictDetection = 935 decodedAlertOnPostCommitConflictDetection; 936 createConflictPreventionDetailsEntry = 937 decodedCreateConflictPreventionDetailsEntry; 938 } 939 catch (final LDAPException le) 940 { 941 Debug.debugException(le); 942 throw le; 943 } 944 catch (final Exception e) 945 { 946 Debug.debugException(e); 947 throw new LDAPException(ResultCode.DECODING_ERROR, 948 ERR_UNIQUENESS_REQ_DECODE_ERROR_DECODING_VALUE.get( 949 StaticUtils.getExceptionMessage(e)), 950 e); 951 } 952 } 953 954 955 956 /** 957 * Retrieves the uniqueness identifier for this control, which may be used to 958 * identify the response control that corresponds to this request control. 959 * This is primarily useful for requests that contain multiple uniqueness 960 * controls, as there may be a separate response control for each. 961 * 962 * @return The uniqueness identifier for this control. 963 */ 964 @NotNull() 965 public String getUniquenessID() 966 { 967 return uniquenessID; 968 } 969 970 971 972 /** 973 * Retrieves the set of attribute types that the server will check for 974 * uniqueness conflicts. 975 * 976 * @return The set of attribute types that the server will check for 977 * uniqueness conflicts, or an empty set if only a filter should be 978 * used to identify conflicts. 979 */ 980 @NotNull() 981 public Set<String> getAttributeTypes() 982 { 983 return attributeTypes; 984 } 985 986 987 988 /** 989 * Retrieves the behavior that the server should exhibit if multiple attribute 990 * types are configured. 991 * 992 * @return The behavior that the server should exhibit if multiple attribute 993 * types are configured. 994 */ 995 @NotNull() 996 public UniquenessMultipleAttributeBehavior getMultipleAttributeBehavior() 997 { 998 return multipleAttributeBehavior; 999 } 1000 1001 1002 1003 /** 1004 * Retrieves the base DN that will be used for searches used to identify 1005 * uniqueness conflicts, if defined. 1006 * 1007 * @return The base DN that will be used for searches used to identify 1008 * uniqueness conflicts, or {@code null} if the server should search 1009 * below all public naming contexts. 1010 */ 1011 @Nullable() 1012 public String getBaseDN() 1013 { 1014 return baseDN; 1015 } 1016 1017 1018 1019 /** 1020 * Retrieves a filter that will be used to identify uniqueness conflicts, if 1021 * defined. 1022 * 1023 * @return A filter that will be used to identify uniqueness conflicts, or 1024 * {@code null} if no filter has been defined. 1025 */ 1026 @Nullable() 1027 public Filter getFilter() 1028 { 1029 return filter; 1030 } 1031 1032 1033 1034 /** 1035 * Indicates whether the server should attempt to identify conflicts with 1036 * soft-deleted entries. 1037 * 1038 * @return {@code true} if the server should identify conflicts with both 1039 * regular entries and soft-deleted entries, or {@code false} if the 1040 * server should only identify conflicts with regular entries. 1041 */ 1042 public boolean preventConflictsWithSoftDeletedEntries() 1043 { 1044 return preventConflictsWithSoftDeletedEntries; 1045 } 1046 1047 1048 1049 /** 1050 * Retrieves the pre-commit validation level, which will be used to identify 1051 * any conflicts before the associated request is processed. 1052 * 1053 * @return The pre-commit validation level. 1054 */ 1055 @NotNull() 1056 public UniquenessValidationLevel getPreCommitValidationLevel() 1057 { 1058 return preCommitValidationLevel; 1059 } 1060 1061 1062 1063 /** 1064 * Retrieves the post-commit validation level, which will be used to identify 1065 * any conflicts that were introduced by the request with which the control is 1066 * associated, or by some other concurrent changed processed in the server. 1067 * 1068 * @return The post-commit validation level. 1069 */ 1070 @NotNull() 1071 public UniquenessValidationLevel getPostCommitValidationLevel() 1072 { 1073 return postCommitValidationLevel; 1074 } 1075 1076 1077 1078 /** 1079 * Indicates whether the server should raise an administrative alert if a 1080 * conflict is detected during post-commit validation processing. 1081 * 1082 * @return {@code true} if the server should raise an administrative alert if 1083 * a conflict is detected during post-commit validation processing, 1084 * or {@code false} if not. 1085 */ 1086 public boolean alertOnPostCommitConflictDetection() 1087 { 1088 return alertOnPostCommitConflictDetection; 1089 } 1090 1091 1092 1093 /** 1094 * Indicates whether the server should create a temporary conflict prevention 1095 * details entry before beginning pre-commit validation to provide better 1096 * support for preventing conflicts. If created, the entry will be removed 1097 * after post-commit validation processing has completed. 1098 * 1099 * @return {@code true} if the server should create a temporary conflict 1100 * prevention details entry before beginning pre-commit validation, 1101 * or {@code false} if not. 1102 */ 1103 public boolean createConflictPreventionDetailsEntry() 1104 { 1105 return createConflictPreventionDetailsEntry; 1106 } 1107 1108 1109 1110 /** 1111 * {@inheritDoc} 1112 */ 1113 @Override() 1114 @NotNull() 1115 public String getControlName() 1116 { 1117 return INFO_UNIQUENESS_REQ_CONTROL_NAME.get(); 1118 } 1119 1120 1121 1122 /** 1123 * Retrieves a representation of this uniqueness request control as a JSON 1124 * object. The JSON object uses the following fields: 1125 * <UL> 1126 * <LI> 1127 * {@code oid} -- A mandatory string field whose value is the object 1128 * identifier for this control. For the uniqueness request control, the 1129 * OID is "1.3.6.1.4.1.30221.2.5.52". 1130 * </LI> 1131 * <LI> 1132 * {@code control-name} -- An optional string field whose value is a 1133 * human-readable name for this control. This field is only intended for 1134 * descriptive purposes, and when decoding a control, the {@code oid} 1135 * field should be used to identify the type of control. 1136 * </LI> 1137 * <LI> 1138 * {@code criticality} -- A mandatory Boolean field used to indicate 1139 * whether this control is considered critical. 1140 * </LI> 1141 * <LI> 1142 * {@code value-base64} -- An optional string field whose value is a 1143 * base64-encoded representation of the raw value for this uniqueness 1144 * request control. Exactly one of the {@code value-base64} and 1145 * {@code value-json} fields must be present. 1146 * </LI> 1147 * <LI> 1148 * {@code value-json} -- An optional JSON object field whose value is a 1149 * user-friendly representation of the value for this uniqueness request 1150 * control. Exactly one of the {@code value-base64} and 1151 * {@code value-json} fields must be present, and if the 1152 * {@code value-json} field is used, then it will use the following 1153 * fields: 1154 * <UL> 1155 * <LI> 1156 * {@code uniqueness-id} -- An optional string field that holds a 1157 * unique identifier for this uniqueness control instance. 1158 * </LI> 1159 * <LI> 1160 * {@code attribute-types} -- An optional array field whose values are 1161 * the names of the attribute types for which to impose uniqueness. 1162 * It may be empty or absent if uniqueness should only be enforced 1163 * using a filter. 1164 * </LI> 1165 * <LI> 1166 * {@code multiple-attribute-behavior} -- An optional string field 1167 * whose value indicates the behavior that should be used if multiple 1168 * unique attribute types are requested. If present, the value should 1169 * be one of "{@code unique-within-each-attribute}", 1170 * "{@code unique-across-all-attributes-including-in-the-same-entry}", 1171 * "{@code unique-across-all-attributes-except-in-the-same-entry}", or 1172 * "{@code unique-in-combination}". 1173 * </LI> 1174 * <LI> 1175 * {@code base-dn} -- An optional string field whose value is the base 1176 * DN that will be used for searches used to identify uniqueness 1177 * conflicts. 1178 * </LI> 1179 * <LI> 1180 * {@code filter} -- An optional string field whose value is the 1181 * string representation of a search filter that will be used to 1182 * identify uniqueness conflicts. 1183 * </LI> 1184 * <LI> 1185 * {@code prevent-conflicts-with-soft-deleted-entries} -- An optional 1186 * Boolean field that indicates whether the server should consider 1187 * soft-deleted entries when looking for conflicts. 1188 * </LI> 1189 * <LI> 1190 * {@code pre-commit-validation-level} -- A mandatory string field 1191 * whose value specifies the level of validation that the server 1192 * should perform before attempting to apply the change. The value 1193 * must be one of "{@code none}", "{@code all-subtree-views}", 1194 * "{@code all-backend-sets}", or 1195 * "{@code all-available-backend-servers}". 1196 * </LI> 1197 * <LI> 1198 * {@code post-commit-validation-level} -- A mandatory string field 1199 * whose value specifies the level of validation that the server 1200 * should perform after applying the change. The value must be one of 1201 * "{@code none}", "{@code all-subtree-views}", 1202 * "{@code all-backend-sets}", or 1203 * "{@code all-available-backend-servers}". 1204 * </LI> 1205 * <LI> 1206 * {@code alert-on-post-commit-conflict-detection} -- An optional 1207 * Boolean field that indicates whether the server should raise an 1208 * administrative alert if a conflict was detected after the change 1209 * was applied. 1210 * </LI> 1211 * <LI> 1212 * {@code create-conflict-prevention-details-entry} -- An optional 1213 * Boolean field that indicates whether the server should create a 1214 * temporary entry that can improve its ability to detect conflicts 1215 * before they happen. 1216 * </LI> 1217 * </UL> 1218 * </LI> 1219 * </UL> 1220 * 1221 * @return A JSON object that contains a representation of this control. 1222 */ 1223 @Override() 1224 @NotNull() 1225 public JSONObject toJSONControl() 1226 { 1227 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 1228 valueFields.put(JSON_FIELD_UNIQUENESS_ID, new JSONString(uniquenessID)); 1229 1230 if (! attributeTypes.isEmpty()) 1231 { 1232 final List<JSONValue> attributeTypesValues = 1233 new ArrayList<>(attributeTypes.size()); 1234 for (final String attributeType : attributeTypes) 1235 { 1236 attributeTypesValues.add(new JSONString(attributeType)); 1237 } 1238 valueFields.put(JSON_FIELD_ATTRIBUTE_TYPES, 1239 new JSONArray(attributeTypesValues)); 1240 } 1241 1242 if ((attributeTypes.size() > 1) || 1243 (multipleAttributeBehavior != UniquenessMultipleAttributeBehavior. 1244 UNIQUE_WITHIN_EACH_ATTRIBUTE)) 1245 { 1246 switch (multipleAttributeBehavior) 1247 { 1248 case UNIQUE_WITHIN_EACH_ATTRIBUTE: 1249 valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR, 1250 new JSONString(JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE)); 1251 break; 1252 case UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY: 1253 valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR, 1254 new JSONString( 1255 JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY)); 1256 break; 1257 case UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY: 1258 valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR, 1259 new JSONString( 1260 JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY)); 1261 break; 1262 case UNIQUE_IN_COMBINATION: 1263 valueFields.put(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR, 1264 new JSONString(JSON_MAB_UNIQUE_IN_COMBINATION)); 1265 break; 1266 } 1267 } 1268 1269 if (baseDN != null) 1270 { 1271 valueFields.put(JSON_FIELD_BASE_DN, new JSONString(baseDN)); 1272 } 1273 1274 if (filter != null) 1275 { 1276 valueFields.put(JSON_FIELD_FILTER, new JSONString(filter.toString())); 1277 } 1278 1279 valueFields.put(JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES, 1280 new JSONBoolean(preventConflictsWithSoftDeletedEntries)); 1281 1282 addJSONValidationLevel(valueFields, JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL, 1283 preCommitValidationLevel); 1284 1285 addJSONValidationLevel(valueFields, JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL, 1286 postCommitValidationLevel); 1287 1288 valueFields.put(JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION, 1289 new JSONBoolean(alertOnPostCommitConflictDetection)); 1290 1291 valueFields.put(JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY, 1292 new JSONBoolean(createConflictPreventionDetailsEntry)); 1293 1294 return new JSONObject( 1295 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 1296 UNIQUENESS_REQUEST_OID), 1297 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 1298 INFO_UNIQUENESS_REQ_CONTROL_NAME.get()), 1299 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 1300 isCritical()), 1301 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 1302 new JSONObject(valueFields))); 1303 } 1304 1305 1306 1307 /** 1308 * Updates the provided map with a JSON field for the given validation level. 1309 * 1310 * @param valueFields The map to which the field should be added. It 1311 * must not be {@code null}./ 1312 * @param fieldName The name to use for the field. It must not be 1313 * {@code null}. 1314 * @param validationLevel The validation level value to use. It must not be 1315 * {@code null}. 1316 */ 1317 private static void addJSONValidationLevel( 1318 @NotNull final Map<String,JSONValue> valueFields, 1319 @NotNull final String fieldName, 1320 @NotNull final UniquenessValidationLevel validationLevel) 1321 { 1322 switch (validationLevel) 1323 { 1324 case NONE: 1325 valueFields.put(fieldName, new JSONString(JSON_VALIDATION_LEVEL_NONE)); 1326 break; 1327 case ALL_SUBTREE_VIEWS: 1328 valueFields.put(fieldName, 1329 new JSONString(JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS)); 1330 break; 1331 case ALL_BACKEND_SETS: 1332 valueFields.put(fieldName, 1333 new JSONString(JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS)); 1334 break; 1335 case ALL_AVAILABLE_BACKEND_SERVERS: 1336 valueFields.put(fieldName, new JSONString( 1337 JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS)); 1338 break; 1339 } 1340 } 1341 1342 1343 1344 /** 1345 * Attempts to decode the provided object as a JSON representation of a 1346 * uniqueness request control. 1347 * 1348 * @param controlObject The JSON object to be decoded. It must not be 1349 * {@code null}. 1350 * @param strict Indicates whether to use strict mode when decoding 1351 * the provided JSON object. If this is {@code true}, 1352 * then this method will throw an exception if the 1353 * provided JSON object contains any unrecognized 1354 * fields. If this is {@code false}, then unrecognized 1355 * fields will be ignored. 1356 * 1357 * @return The uniqueness request control that was decoded from the provided 1358 * JSON object. 1359 * 1360 * @throws LDAPException If the provided JSON object cannot be parsed as a 1361 * valid uniqueness request control. 1362 */ 1363 @NotNull() 1364 public static UniquenessRequestControl decodeJSONControl( 1365 @NotNull final JSONObject controlObject, 1366 final boolean strict) 1367 throws LDAPException 1368 { 1369 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 1370 controlObject, strict, true, true); 1371 1372 final ASN1OctetString rawValue = jsonControl.getRawValue(); 1373 if (rawValue != null) 1374 { 1375 return new UniquenessRequestControl(new Control( 1376 jsonControl.getOID(), jsonControl.getCriticality(), rawValue)); 1377 } 1378 1379 1380 final JSONObject valueObject = jsonControl.getValueObject(); 1381 1382 final String uniquenessID = 1383 valueObject.getFieldAsString(JSON_FIELD_UNIQUENESS_ID); 1384 1385 final Set<String> attributeTypes; 1386 final List<JSONValue> attributeTypesValues = 1387 valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTE_TYPES); 1388 if ((attributeTypesValues == null) || attributeTypesValues.isEmpty()) 1389 { 1390 attributeTypes = Collections.emptySet(); 1391 } 1392 else 1393 { 1394 attributeTypes = new LinkedHashSet<>(); 1395 for (final JSONValue v : attributeTypesValues) 1396 { 1397 if (v instanceof JSONString) 1398 { 1399 attributeTypes.add(((JSONString) v).stringValue()); 1400 } 1401 else 1402 { 1403 throw new LDAPException(ResultCode.DECODING_ERROR, 1404 ERR_UNIQUENESS_REQ_JSON_ATTR_TYPE_NOT_STRING.get( 1405 controlObject.toSingleLineString(), 1406 JSON_FIELD_ATTRIBUTE_TYPES)); 1407 } 1408 } 1409 } 1410 1411 final Filter filter; 1412 final String filterStr = valueObject.getFieldAsString(JSON_FIELD_FILTER); 1413 if (filterStr == null) 1414 { 1415 filter = null; 1416 } 1417 else 1418 { 1419 try 1420 { 1421 filter = Filter.create(filterStr); 1422 } 1423 catch (final LDAPException e) 1424 { 1425 Debug.debugException(e); 1426 throw new LDAPException(ResultCode.DECODING_ERROR, 1427 ERR_UNIQUENESS_REQ_JSON_INVALID_FILTER.get( 1428 controlObject.toSingleLineString(), JSON_FIELD_FILTER, 1429 filterStr)); 1430 } 1431 } 1432 1433 1434 final UniquenessRequestControlProperties properties; 1435 if (attributeTypes.isEmpty()) 1436 { 1437 if (filter == null) 1438 { 1439 throw new LDAPException(ResultCode.DECODING_ERROR, 1440 ERR_UNIQUENESS_REQ_JSON_NEITHER_ATTR_TYPES_NOR_FILTER.get( 1441 controlObject.toSingleLineString(), 1442 JSON_FIELD_ATTRIBUTE_TYPES, JSON_FIELD_FILTER)); 1443 } 1444 else 1445 { 1446 properties = new UniquenessRequestControlProperties(filter); 1447 } 1448 } 1449 else 1450 { 1451 properties = new UniquenessRequestControlProperties(attributeTypes); 1452 1453 if (filter != null) 1454 { 1455 properties.setFilter(filter); 1456 } 1457 } 1458 1459 1460 final String multipleAttributeBehaviorStr = 1461 valueObject.getFieldAsString(JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR); 1462 if (multipleAttributeBehaviorStr == null) 1463 { 1464 properties.setMultipleAttributeBehavior( 1465 UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE); 1466 } 1467 else 1468 { 1469 switch (multipleAttributeBehaviorStr) 1470 { 1471 case JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE: 1472 properties.setMultipleAttributeBehavior( 1473 UniquenessMultipleAttributeBehavior. 1474 UNIQUE_WITHIN_EACH_ATTRIBUTE); 1475 break; 1476 case JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY: 1477 properties.setMultipleAttributeBehavior( 1478 UniquenessMultipleAttributeBehavior. 1479 UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_IN_SAME_ENTRY); 1480 break; 1481 case JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY: 1482 properties.setMultipleAttributeBehavior( 1483 UniquenessMultipleAttributeBehavior. 1484 UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_IN_SAME_ENTRY); 1485 break; 1486 case JSON_MAB_UNIQUE_IN_COMBINATION: 1487 properties.setMultipleAttributeBehavior( 1488 UniquenessMultipleAttributeBehavior.UNIQUE_IN_COMBINATION); 1489 break; 1490 default: 1491 throw new LDAPException(ResultCode.DECODING_ERROR, 1492 ERR_UNIQUENESS_REQ_JSON_UNRECOGNIZED_MULTIPLE_ATTR_BEHAVIOR.get( 1493 controlObject.toSingleLineString(), 1494 JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR, 1495 multipleAttributeBehaviorStr, 1496 JSON_MAB_UNIQUE_WITHIN_EACH_ATTRIBUTE, 1497 JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_INCLUDING_SAME_ENTRY, 1498 JSON_MAB_UNIQUE_ACROSS_ALL_ATTRIBUTES_EXCEPT_SAME_ENTRY, 1499 JSON_MAB_UNIQUE_IN_COMBINATION)); 1500 } 1501 } 1502 1503 1504 final String baseDNStr = valueObject.getFieldAsString(JSON_FIELD_BASE_DN); 1505 if (baseDNStr != null) 1506 { 1507 properties.setBaseDN(baseDNStr); 1508 } 1509 1510 1511 final Boolean preventConflictsWithSoftDeletedEntries = 1512 valueObject.getFieldAsBoolean( 1513 JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES); 1514 if (preventConflictsWithSoftDeletedEntries == null) 1515 { 1516 properties.setPreventConflictsWithSoftDeletedEntries(false); 1517 } 1518 else 1519 { 1520 properties.setPreventConflictsWithSoftDeletedEntries( 1521 preventConflictsWithSoftDeletedEntries); 1522 } 1523 1524 1525 properties.setPreCommitValidationLevel(getValidationLevelJSON( 1526 controlObject, valueObject, JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL)); 1527 1528 1529 properties.setPostCommitValidationLevel(getValidationLevelJSON( 1530 controlObject, valueObject, JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL)); 1531 1532 1533 final Boolean alertOnPostCommitConflictDetection = 1534 valueObject.getFieldAsBoolean( 1535 JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION); 1536 if (alertOnPostCommitConflictDetection == null) 1537 { 1538 properties.setAlertOnPostCommitConflictDetection(true); 1539 } 1540 else 1541 { 1542 properties.setAlertOnPostCommitConflictDetection( 1543 alertOnPostCommitConflictDetection); 1544 } 1545 1546 1547 final Boolean createConflictPreventionDetailsEntry = 1548 valueObject.getFieldAsBoolean( 1549 JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY); 1550 if (createConflictPreventionDetailsEntry == null) 1551 { 1552 properties.setCreateConflictPreventionDetailsEntry(false); 1553 } 1554 else 1555 { 1556 properties.setCreateConflictPreventionDetailsEntry( 1557 createConflictPreventionDetailsEntry); 1558 } 1559 1560 1561 if (strict) 1562 { 1563 final List<String> unrecognizedFields = 1564 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1565 valueObject, JSON_FIELD_UNIQUENESS_ID, 1566 JSON_FIELD_ATTRIBUTE_TYPES, 1567 JSON_FIELD_MULTIPLE_ATTRIBUTE_BEHAVIOR, 1568 JSON_FIELD_BASE_DN, JSON_FIELD_FILTER, 1569 JSON_FIELD_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES, 1570 JSON_FIELD_PRE_COMMIT_VALIDATION_LEVEL, 1571 JSON_FIELD_POST_COMMIT_VALIDATION_LEVEL, 1572 JSON_FIELD_ALERT_ON_POST_COMMIT_CONFLICT_DETECTION, 1573 JSON_FIELD_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY); 1574 if (! unrecognizedFields.isEmpty()) 1575 { 1576 throw new LDAPException(ResultCode.DECODING_ERROR, 1577 ERR_UNIQUENESS_REQ_JSON_UNRECOGNIZED_FIELD.get( 1578 controlObject.toSingleLineString(), 1579 unrecognizedFields.get(0))); 1580 } 1581 } 1582 1583 1584 return new UniquenessRequestControl(jsonControl.getCriticality(), 1585 uniquenessID, properties); 1586 } 1587 1588 1589 1590 /** 1591 * Retrieves the uniqueness validation level value contained in the specified 1592 * field of the given value object. 1593 * 1594 * @param controlObject The JSON object containing an encoded representation 1595 * of the control being decoded. It must not be 1596 * {@code null}. 1597 * @param valueObject The JSON object from which the value is to be 1598 * retrieved. It must not be {@code null}. 1599 * @param fieldName The name of the field that is expected to contain 1600 * the value. It must not be {@code null}. 1601 * 1602 * @return The uniqueness validation level value that was retrieved. 1603 * 1604 * @throws LDAPException If the value object does not have a valid 1605 * uniqueness validation level value in the specified 1606 * field. 1607 */ 1608 @NotNull() 1609 private static UniquenessValidationLevel getValidationLevelJSON( 1610 @NotNull final JSONObject controlObject, 1611 @NotNull final JSONObject valueObject, 1612 @NotNull final String fieldName) 1613 throws LDAPException 1614 { 1615 final String valueStr = valueObject.getFieldAsString(fieldName); 1616 if (valueStr == null) 1617 { 1618 throw new LDAPException(ResultCode.DECODING_ERROR, 1619 ERR_UNIQUENESS_REQ_JSON_MISSING_FIELD.get( 1620 controlObject.toSingleLineString(), fieldName)); 1621 } 1622 1623 switch (valueStr) 1624 { 1625 case JSON_VALIDATION_LEVEL_NONE: 1626 return UniquenessValidationLevel.NONE; 1627 case JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS: 1628 return UniquenessValidationLevel.ALL_SUBTREE_VIEWS; 1629 case JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS: 1630 return UniquenessValidationLevel.ALL_BACKEND_SETS; 1631 case JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS: 1632 return UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS; 1633 default: 1634 throw new LDAPException(ResultCode.DECODING_ERROR, 1635 ERR_UNIQUENESS_REQ_JSON_UNRECOGNIZED_VALIDATION_LEVEL.get( 1636 controlObject.toSingleLineString(), fieldName, valueStr, 1637 JSON_VALIDATION_LEVEL_NONE, 1638 JSON_VALIDATION_LEVEL_ALL_SUBTREE_VIEWS, 1639 JSON_VALIDATION_LEVEL_ALL_BACKEND_SETS, 1640 JSON_VALIDATION_LEVEL_ALL_AVAILABLE_BACKEND_SERVERS)); 1641 } 1642 } 1643 1644 1645 1646 /** 1647 * {@inheritDoc} 1648 */ 1649 @Override() 1650 public void toString(@NotNull final StringBuilder buffer) 1651 { 1652 buffer.append("UniquenessRequestControl(isCritical="); 1653 buffer.append(isCritical()); 1654 buffer.append(", uniquenessID='"); 1655 buffer.append(uniquenessID); 1656 buffer.append("', attributeTypes={"); 1657 1658 final Iterator<String> attributeTypesIterator = attributeTypes.iterator(); 1659 while (attributeTypesIterator.hasNext()) 1660 { 1661 buffer.append('\''); 1662 buffer.append(attributeTypesIterator.next()); 1663 buffer.append('\''); 1664 1665 if (attributeTypesIterator.hasNext()) 1666 { 1667 buffer.append(", "); 1668 } 1669 } 1670 1671 buffer.append("}, multipleAttributeBehavior="); 1672 buffer.append(multipleAttributeBehavior); 1673 1674 if (baseDN != null) 1675 { 1676 buffer.append(", baseDN='"); 1677 buffer.append(baseDN); 1678 buffer.append('\''); 1679 } 1680 1681 if (filter != null) 1682 { 1683 buffer.append(", filter='"); 1684 buffer.append(filter); 1685 buffer.append('\''); 1686 } 1687 1688 buffer.append(", preventConflictsWithSoftDeletedEntries="); 1689 buffer.append(preventConflictsWithSoftDeletedEntries); 1690 buffer.append(", preCommitValidationLevel="); 1691 buffer.append(preCommitValidationLevel); 1692 buffer.append(", postCommitValidationLevel="); 1693 buffer.append(postCommitValidationLevel); 1694 buffer.append(", alertOnPostCommitConflictDetection="); 1695 buffer.append(alertOnPostCommitConflictDetection); 1696 buffer.append(", createConflictPreventionDetailsEntry="); 1697 buffer.append(createConflictPreventionDetailsEntry); 1698 buffer.append(')'); 1699 } 1700}