001/* 002 * Copyright 2017-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2017-2022 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-2022 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.LinkedHashSet; 044import java.util.Set; 045 046import com.unboundid.asn1.ASN1Boolean; 047import com.unboundid.asn1.ASN1Element; 048import com.unboundid.asn1.ASN1Enumerated; 049import com.unboundid.asn1.ASN1OctetString; 050import com.unboundid.asn1.ASN1Sequence; 051import com.unboundid.asn1.ASN1Set; 052import com.unboundid.ldap.sdk.Control; 053import com.unboundid.ldap.sdk.Filter; 054import com.unboundid.ldap.sdk.LDAPException; 055import com.unboundid.ldap.sdk.ResultCode; 056import com.unboundid.util.CryptoHelper; 057import com.unboundid.util.Debug; 058import com.unboundid.util.NotMutable; 059import com.unboundid.util.NotNull; 060import com.unboundid.util.Nullable; 061import com.unboundid.util.StaticUtils; 062import com.unboundid.util.ThreadSafety; 063import com.unboundid.util.ThreadSafetyLevel; 064import com.unboundid.util.Validator; 065 066import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 067 068 069 070/** 071 * This class provides a request control that may be included in an add, modify, 072 * or modify DN request to ensure that the contents of that request will not 073 * result in a uniqueness conflict with any other entry in the server. Each 074 * instance of this control should define exactly one uniqueness constraint for 075 * the associated operation. Multiple instances of this control can be included 076 * in the same request to define multiple independent uniqueness constraints 077 * that must all be satisfied. If any of the uniqueness constraints is not 078 * satisfied, then the corresponding LDAP result should have a result code of 079 * {@link ResultCode#ASSERTION_FAILED} and a {@link UniquenessResponseControl} 080 * for each uniqueness constraint that was not satisfied. 081 * <BR> 082 * <BLOCKQUOTE> 083 * <B>NOTE:</B> This class, and other classes within the 084 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 085 * supported for use against Ping Identity, UnboundID, and 086 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 087 * for proprietary functionality or for external specifications that are not 088 * considered stable or mature enough to be guaranteed to work in an 089 * interoperable way with other types of LDAP servers. 090 * </BLOCKQUOTE> 091 * <BR> 092 * The request properties must contain either one or more attribute types, a 093 * filter, or both. If only a filter is specified, then the server will use 094 * that filter to identify conflicts (for an add request, any matches at all 095 * will be considered a conflict; for a modify or modify DN request, any matches 096 * with any entry other than the one being updated will be considered a 097 * conflict). If a single attribute type is specified with no filter, then any 098 * change that would result in multiple entries having the same value for that 099 * attribute will be considered a conflict. If multiple attribute types are 100 * specified, then the multiple attribute behavior will be used to determine how 101 * to identify conflicts, as documented in the 102 * {@link UniquenessMultipleAttributeBehavior} enum. If both a set of attribute 103 * types and a filter are provided, then only entries matching both sets of 104 * criteria will be considered a conflict. 105 * <BR><BR> 106 * The server can perform two different searches in an attempt to identify 107 * conflicts. In the pre-commit phase, it will attempt to identify any 108 * conflicts that already exist, and will reject the associated change if there 109 * are any. In the post-commit phase, it can see if there were any conflicts 110 * introduced by the change itself or by another change happening at the same 111 * time. If a conflict is detected in the post-commit phase, then the server 112 * won't have prevented it, but at least the control can be used to provide 113 * notification about it. The server may also raise an administrative alert to 114 * notify administrators about the conflict. 115 * <BR><BR> 116 * Although post-commit validation on its own should be able to detect conflicts 117 * that arise as a result of concurrent changes in other instances, it is also 118 * possible to take additional measures to help prevent conflicts from arising 119 * in the first place. The control may indicate that the server should create 120 * a temporary conflict prevention details entry before beginning pre-commit 121 * validation processing. This entry may be found during pre-commit validation 122 * performed for any conflicting concurrent updates so that the conflicting 123 * operation is rejected. This temporary entry will be automatically removed 124 * after uniqueness processing has completed, regardless of its success or 125 * failure. 126 * <BR><BR> 127 * This request control may be sent either directly to a Directory Server 128 * instance, or it may be sent to a Directory Proxy Server with or without entry 129 * balancing. If the request is sent directly to a Directory Server, then only 130 * that one server will be checked for uniqueness conflicts, and it is possible 131 * that concurrent conflicts may be introduced on other servers that have not 132 * yet been replicated by the time control processing has completed. If the 133 * request is sent to a Directory Proxy Server instance, then search may be 134 * processed in one or more backend servers based on the pre-commit and 135 * post-commit validation levels, and at the most paranoid levels, it is highly 136 * unlikely that any conflicts will go unnoticed. 137 * <BR><BR> 138 * The request control has an OID of 1.3.6.1.4.1.30221.2.5.52, a criticality of 139 * either {@code true} or {@code false}, and a value with the following 140 * encoding: 141 * <PRE> 142 * UniquenessRequestValue ::= SEQUENCE { 143 * uniquenessID [0] OCTET STRING, 144 * attributeTypes [1] SET OF OCTET STRING OPTIONAL, 145 * multipleAttributeBehavior [2] ENUMERATED { 146 * uniqueWithinEachAttribute (0), 147 * uniqueAcrossAllAttributesIncludingInSameEntry (1), 148 * uniqueAcrossAllAttributesExceptInSameEntry (2), 149 * uniqueInCombination (3), 150 * ... } DEFAULT uniqueWithinEachAttribute, 151 * baseDN [3] LDAPDN OPTIONAL, 152 * filter [4] Filter OPTIONAL, 153 * preventConflictsWithSoftDeletedEntries [5] BOOLEAN DEFAULT FALSE, 154 * preCommitValidationLevel [6] ENUMERATED { 155 * none (0), 156 * allSubtreeViews (1), 157 * allBackendSets (2), 158 * allAvailableBackendServers (3), 159 * ... } DEFAULT allSubtreeViews, 160 * postCommitValidationLevel [7] ENUMERATED { 161 * none (0), 162 * allSubtreeViews (1), 163 * allBackendSets (2), 164 * allAvailableBackendServers (3), 165 * ... } DEFAULT allSubtreeViews, 166 * alertOnPostCommitConflictDetection [8] BOOLEAN DEFAULT TRUE, 167 * createConflictPreventionDetailsEntry [9] BOOLEAN DEFAULT FALSE, 168 * ... } 169 * </PRE> 170 * <BR><BR> 171 * <H2>Example</H2> 172 * The following example demonstrates how to use the uniqueness request control 173 * to only process an add operation if it does not result in multiple entries 174 * that have the same uid value: 175 * <BR><BR> 176 * <PRE> 177 * // Create the properties to build a uniqueness request control that 178 * // will try to prevent an add operation from creating a new entry 179 * // that has the same uid as an existing entry in the server. During 180 * // pre-commit processing (which happens before the server actually 181 * // processes the add), the server will check at least one server in 182 * // each entry-balancing backend set (or just one server in a 183 * // non-entry-balanced deployment). During post-commit processing 184 * // (which happens if the add succeeds), the server will double-check 185 * // that no conflicting entry was added on any available server in the 186 * // topology. Also ensure that the server will not allow conflicts 187 * // with soft-deleted entries. 188 * final UniquenessRequestControlProperties uniquenessProperties = 189 * new UniquenessRequestControlProperties("uid"); 190 * uniquenessProperties.setPreCommitValidationLevel( 191 * UniquenessValidationLevel.ALL_BACKEND_SETS); 192 * uniquenessProperties.setPostCommitValidationLevel( 193 * UniquenessValidationLevel.ALL_AVAILABLE_BACKEND_SERVERS); 194 * uniquenessProperties.setPreventConflictsWithSoftDeletedEntries(true); 195 * 196 * // Create the request control. It will be critical so that the 197 * // server will not attempt to process the add if it can't honor the 198 * // uniqueness request. 199 * final boolean isCritical = true; 200 * final String uniquenessID = "uid-uniqueness"; 201 * final UniquenessRequestControl uniquenessRequestControl = 202 * new UniquenessRequestControl(isCritical, uniquenessID, 203 * uniquenessProperties); 204 * 205 * // Attach the control to an add request. 206 * addRequest.addControl(uniquenessRequestControl); 207 * 208 * // Send the add request to the server and read the result. 209 * try 210 * { 211 * final LDAPResult addResult = connection.add(addRequest); 212 * 213 * // The add operation succeeded, so the entry should have been 214 * // created, but there is still the possibility that a post-commit 215 * // conflict was discovered, indicating that another request 216 * // processed at about the same time as our add introduced a 217 * // conflicting entry. 218 * final Map<String,UniquenessResponseControl> uniquenessResponses; 219 * try 220 * { 221 * uniquenessResponses = UniquenessResponseControl.get(addResult); 222 * } 223 * catch (final LDAPException e) 224 * { 225 * throw new RuntimeException( 226 * "The add succeeded, but an error occurred while trying " + 227 * "to decode a uniqueness response control in add " + 228 * "result " + addResult + ": " + 229 * StaticUtils.getExceptionMessage(e), 230 * e); 231 * } 232 * 233 * final UniquenessResponseControl uniquenessResponseControl = 234 * uniquenessResponses.get(uniquenessID); 235 * if ((uniquenessResponseControl != null) && 236 * uniquenessResponseControl.uniquenessConflictFound()) 237 * { 238 * throw new RuntimeException( 239 * "The add succeeded, but a uniqueness conflict was found " + 240 * "Uniqueness validation message: " + 241 * uniquenessResponseControl.getValidationMessage()); 242 * } 243 * } 244 * catch (final LDAPException e) 245 * { 246 * // The add attempt failed. It might have been because of a 247 * // uniqueness problem, or it could have been for some other reason. 248 * // To figure out which it was, look to see if there is an 249 * // appropriate uniqueness response control. 250 * final Map<String, UniquenessResponseControl> uniquenessResponses; 251 * try 252 * { 253 * uniquenessResponses = 254 * UniquenessResponseControl.get(e.toLDAPResult()); 255 * } 256 * catch (final LDAPException e2) 257 * { 258 * throw new LDAPException(e.getResultCode(), 259 * "The add attempt failed with result " + e.toLDAPResult() + 260 * ", and an error occurred while trying to decode a " + 261 * "uniqueness response control in the result: " + 262 * StaticUtils.getExceptionMessage(e2), 263 * e); 264 * } 265 * 266 * final UniquenessResponseControl uniquenessResponseControl = 267 * uniquenessResponses.get(uniquenessID); 268 * if (uniquenessResponseControl == null) 269 * { 270 * // The add result didn't include a uniqueness response control, 271 * // indicating that the failure was not because of a uniqueness 272 * // conflict. 273 * throw e; 274 * } 275 * 276 * if (uniquenessResponseControl.uniquenessConflictFound()) 277 * { 278 * // The add failed, and the uniqueness response control indicates 279 * // that the failure was because of a uniqueness conflict. 280 * 281 * final UniquenessValidationResult preCommitResult = 282 * uniquenessResponseControl.getPreCommitValidationResult(); 283 * final UniquenessValidationResult postCommitResult = 284 * uniquenessResponseControl.getPreCommitValidationResult(); 285 * final String validationMessage = 286 * uniquenessResponseControl.getValidationMessage(); 287 * 288 * throw e; 289 * } 290 * else 291 * { 292 * // The add failed, but the uniqueness response control indicates 293 * // that the failure was not because of a uniqueness conflict. 294 * throw e; 295 * } 296 * } 297 * </PRE> 298 */ 299@NotMutable() 300@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 301public final class UniquenessRequestControl 302 extends Control 303{ 304 /** 305 * The OID (1.3.6.1.4.1.30221.2.5.52) for the uniqueness request control. 306 */ 307 @NotNull public static final String UNIQUENESS_REQUEST_OID = 308 "1.3.6.1.4.1.30221.2.5.52"; 309 310 311 312 /** 313 * The BER type for the uniqueness ID element in the value sequence. 314 */ 315 private static final byte TYPE_UNIQUENESS_ID = (byte) 0x80; 316 317 318 319 /** 320 * The BER type for the attribute types element in the value sequence. 321 */ 322 private static final byte TYPE_ATTRIBUTE_TYPES = (byte) 0xA1; 323 324 325 326 /** 327 * The BER type for the multiple attribute behavior element in the value 328 * sequence. 329 */ 330 private static final byte TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR = (byte) 0x82; 331 332 333 334 /** 335 * The BER type for the base DN element in the value sequence. 336 */ 337 private static final byte TYPE_BASE_DN = (byte) 0x83; 338 339 340 341 /** 342 * The BER type for the filter element in the value sequence. 343 */ 344 private static final byte TYPE_FILTER = (byte) 0xA4; 345 346 347 348 /** 349 * The BER type for the prevent conflicts with soft-deleted entries element in 350 * the value sequence. 351 */ 352 private static final byte TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES = 353 (byte) 0x85; 354 355 356 357 /** 358 * The BER type for the pre-commit validation element in the value sequence. 359 */ 360 private static final byte TYPE_PRE_COMMIT_VALIDATION_LEVEL = (byte) 0x86; 361 362 363 364 /** 365 * The BER type for the post-commit validation element in the value sequence. 366 */ 367 private static final byte TYPE_POST_COMMIT_VALIDATION_LEVEL = (byte) 0x87; 368 369 370 371 /** 372 * The BER type for the value sequence element that indicates whether to 373 * raise an administrative alert if a conflict is detected during post-commit 374 * validation. 375 */ 376 private static final byte TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION = 377 (byte) 0x88; 378 379 380 381 /** 382 * The BER type for the value sequence element that indicates whether to 383 * create a conflict prevention details entry before pre-commit validation as 384 * a means of helping to avoid conflicts. 385 */ 386 private static final byte TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY = 387 (byte) 0x89; 388 389 390 391 /** 392 * The serial version UID for this serializable class. 393 */ 394 private static final long serialVersionUID = 7976218379635922852L; 395 396 397 398 // Indicates whether the server should raise an administrative alert if a 399 // conflict is detected during post-commit validation. 400 private final boolean alertOnPostCommitConflictDetection; 401 402 // Indicates whether the server should create a conflict prevention details 403 // entry before pre-commit validation as a means of helping to avoid 404 // conflicts. 405 private final boolean createConflictPreventionDetailsEntry; 406 407 // Indicates whether to prevent conflicts with soft-deleted entries. 408 private final boolean preventConflictsWithSoftDeletedEntries; 409 410 // An optional filter that should be used in the course of identifying 411 // uniqueness conflicts. 412 @Nullable private final Filter filter; 413 414 // A potentially-empty set of attribute types that should be checked for 415 // uniqueness conflicts. 416 @NotNull private final Set<String> attributeTypes; 417 418 // An optional base DN to use when checking for conflicts. 419 @Nullable private final String baseDN; 420 421 // A value that will be used to correlate this request control with its 422 // corresponding response control. 423 @NotNull private final String uniquenessID; 424 425 // The behavior that the server should exhibit if multiple attribute types 426 // are configured. 427 @NotNull private final UniquenessMultipleAttributeBehavior 428 multipleAttributeBehavior; 429 430 // The level of validation that the server should perform before processing 431 // the associated change. 432 @NotNull private final UniquenessValidationLevel postCommitValidationLevel; 433 434 // The level of validation that the server should perform after processing the 435 // associated change. 436 @NotNull private final UniquenessValidationLevel preCommitValidationLevel; 437 438 439 440 /** 441 * Creates a new uniqueness request control with the provided information. 442 * 443 * @param isCritical Indicates whether the control should be considered 444 * critical. 445 * @param uniquenessID A value that will be used to correlate this request 446 * control with its corresponding response control. If 447 * this is {@code null}, then a unique identifier will 448 * be automatically generated. 449 * @param properties The set of properties for this control. It must not 450 * be {@code null}. 451 * 452 * @throws LDAPException If the provided properties cannot be used to create 453 * a valid uniqueness request control. 454 */ 455 public UniquenessRequestControl(final boolean isCritical, 456 @Nullable final String uniquenessID, 457 @NotNull final UniquenessRequestControlProperties properties) 458 throws LDAPException 459 { 460 this((uniquenessID == null 461 ? CryptoHelper.getRandomUUID().toString() 462 : uniquenessID), 463 properties, isCritical); 464 } 465 466 467 468 /** 469 * Creates a new uniqueness request control with the provided information. 470 * Note that this version of the constructor takes the same set of arguments 471 * as the above constructor, but in a different order (to distinguish between 472 * the two versions), and with the additional constraint that the uniqueness 473 * ID must not be {@code null}. 474 * 475 * @param uniquenessID A value that will be used to correlate this request 476 * control with its corresponding response control. It 477 * must not be {@code null}. 478 * @param properties The set of properties for this control. It must not 479 * be {@code null}. 480 * @param isCritical Indicates whether the control should be considered 481 * critical. 482 * 483 * @throws LDAPException If the provided properties cannot be used to create 484 * a valid uniqueness request control. 485 */ 486 private UniquenessRequestControl(@NotNull final String uniquenessID, 487 @NotNull final UniquenessRequestControlProperties properties, 488 final boolean isCritical) 489 throws LDAPException 490 { 491 super(UNIQUENESS_REQUEST_OID, isCritical, 492 encodeValue(uniquenessID, properties)); 493 494 Validator.ensureNotNull(uniquenessID); 495 this.uniquenessID = uniquenessID; 496 497 attributeTypes = properties.getAttributeTypes(); 498 multipleAttributeBehavior = properties.getMultipleAttributeBehavior(); 499 baseDN = properties.getBaseDN(); 500 filter = properties.getFilter(); 501 preventConflictsWithSoftDeletedEntries = 502 properties.preventConflictsWithSoftDeletedEntries(); 503 preCommitValidationLevel = properties.getPreCommitValidationLevel(); 504 postCommitValidationLevel = properties.getPostCommitValidationLevel(); 505 alertOnPostCommitConflictDetection = 506 properties.alertOnPostCommitConflictDetection(); 507 createConflictPreventionDetailsEntry = 508 properties.createConflictPreventionDetailsEntry(); 509 510 if (attributeTypes.isEmpty() && (filter == null)) 511 { 512 throw new LDAPException(ResultCode.PARAM_ERROR, 513 ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get()); 514 } 515 } 516 517 518 519 /** 520 * Encodes the provided information into an octet string that is suitable for 521 * use as the value of this control. 522 * 523 * @param uniquenessID A value that will be used to correlate this request 524 * control with its corresponding response control. It 525 * must not be {@code null}. 526 * @param properties The set of properties for this control. It must not 527 * be {@code null}. 528 * 529 * @return The encoded value that was created. 530 */ 531 @NotNull() 532 private static ASN1OctetString encodeValue(@NotNull final String uniquenessID, 533 @NotNull final UniquenessRequestControlProperties properties) 534 { 535 final ArrayList<ASN1Element> elements = new ArrayList<>(10); 536 537 elements.add(new ASN1OctetString(TYPE_UNIQUENESS_ID, uniquenessID)); 538 539 final Set<String> attributeTypes = properties.getAttributeTypes(); 540 if (!attributeTypes.isEmpty()) 541 { 542 final ArrayList<ASN1Element> attributeTypeElements = 543 new ArrayList<>(attributeTypes.size()); 544 for (final String attributeType : attributeTypes) 545 { 546 attributeTypeElements.add(new ASN1OctetString(attributeType)); 547 } 548 elements.add(new ASN1Set(TYPE_ATTRIBUTE_TYPES, attributeTypeElements)); 549 } 550 551 final UniquenessMultipleAttributeBehavior multipleAttributeBehavior = 552 properties.getMultipleAttributeBehavior(); 553 if (multipleAttributeBehavior != 554 UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE) 555 { 556 elements.add(new ASN1Enumerated(TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR, 557 multipleAttributeBehavior.intValue())); 558 } 559 560 final String baseDN = properties.getBaseDN(); 561 if (baseDN != null) 562 { 563 elements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN)); 564 } 565 566 final Filter filter = properties.getFilter(); 567 if (filter != null) 568 { 569 elements.add(new ASN1Element(TYPE_FILTER, filter.encode().encode())); 570 } 571 572 if (properties.preventConflictsWithSoftDeletedEntries()) 573 { 574 elements.add(new ASN1Boolean( 575 TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES, true)); 576 } 577 578 final UniquenessValidationLevel preCommitValidationLevel = 579 properties.getPreCommitValidationLevel(); 580 if (preCommitValidationLevel != UniquenessValidationLevel.ALL_SUBTREE_VIEWS) 581 { 582 elements.add(new ASN1Enumerated(TYPE_PRE_COMMIT_VALIDATION_LEVEL, 583 preCommitValidationLevel.intValue())); 584 } 585 586 final UniquenessValidationLevel postCommitValidationLevel = 587 properties.getPostCommitValidationLevel(); 588 if (postCommitValidationLevel != 589 UniquenessValidationLevel.ALL_SUBTREE_VIEWS) 590 { 591 elements.add(new ASN1Enumerated(TYPE_POST_COMMIT_VALIDATION_LEVEL, 592 postCommitValidationLevel.intValue())); 593 } 594 595 if (! properties.alertOnPostCommitConflictDetection()) 596 { 597 elements.add(new ASN1Boolean( 598 TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION, false)); 599 } 600 601 if (properties.createConflictPreventionDetailsEntry()) 602 { 603 elements.add(new ASN1Boolean( 604 TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY, true)); 605 } 606 607 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 608 } 609 610 611 612 /** 613 * Creates a new uniqueness request control that is decoded from the provided 614 * generic control. 615 * 616 * @param control The control to be decoded as a uniqueness request control. 617 * It must not be {@code null}. 618 * 619 * @throws LDAPException If the provided control cannot be decoded as a 620 * valid uniqueness request control. 621 */ 622 public UniquenessRequestControl(@NotNull final Control control) 623 throws LDAPException 624 { 625 super(control); 626 627 final ASN1OctetString value = control.getValue(); 628 if (value == null) 629 { 630 throw new LDAPException(ResultCode.DECODING_ERROR, 631 ERR_UNIQUENESS_REQ_DECODE_NO_VALUE.get()); 632 } 633 634 try 635 { 636 boolean decodedAlertOnPostCommitConflictDetection = true; 637 boolean decodedCreateConflictPreventionDetailsEntry = false; 638 boolean decodedPreventSoftDeletedConflicts = false; 639 Filter decodedFilter = null; 640 Set<String> decodedAttributeTypes = Collections.emptySet(); 641 String decodedBaseDN = null; 642 String decodedUniquenessID = null; 643 UniquenessMultipleAttributeBehavior decodedMultipleAttributeBehavior = 644 UniquenessMultipleAttributeBehavior.UNIQUE_WITHIN_EACH_ATTRIBUTE; 645 UniquenessValidationLevel decodedPreCommitLevel = 646 UniquenessValidationLevel.ALL_SUBTREE_VIEWS; 647 UniquenessValidationLevel decodedPostCommitLevel = 648 UniquenessValidationLevel.ALL_SUBTREE_VIEWS; 649 650 final ASN1Element[] elements = 651 ASN1Sequence.decodeAsSequence(value.getValue()).elements(); 652 for (final ASN1Element e : elements) 653 { 654 switch (e.getType()) 655 { 656 case TYPE_UNIQUENESS_ID: 657 decodedUniquenessID = 658 ASN1OctetString.decodeAsOctetString(e).stringValue(); 659 break; 660 case TYPE_ATTRIBUTE_TYPES: 661 final ASN1Element[] atElements = ASN1Set.decodeAsSet(e).elements(); 662 final LinkedHashSet<String> atNames = new LinkedHashSet<>( 663 StaticUtils.computeMapCapacity(atElements.length)); 664 for (final ASN1Element atElement : atElements) 665 { 666 atNames.add(ASN1OctetString.decodeAsOctetString( 667 atElement).stringValue()); 668 } 669 decodedAttributeTypes = Collections.unmodifiableSet(atNames); 670 break; 671 case TYPE_MULTIPLE_ATTRIBUTE_BEHAVIOR: 672 final int mabIntValue = 673 ASN1Enumerated.decodeAsEnumerated(e).intValue(); 674 decodedMultipleAttributeBehavior = 675 UniquenessMultipleAttributeBehavior.valueOf(mabIntValue); 676 if (decodedMultipleAttributeBehavior == null) 677 { 678 throw new LDAPException(ResultCode.DECODING_ERROR, 679 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_MULTIPLE_ATTR_BEHAVIOR.get( 680 mabIntValue)); 681 } 682 break; 683 case TYPE_BASE_DN: 684 decodedBaseDN = 685 ASN1OctetString.decodeAsOctetString(e).stringValue(); 686 break; 687 case TYPE_FILTER: 688 decodedFilter = Filter.decode(ASN1Element.decode(e.getValue())); 689 break; 690 case TYPE_PREVENT_CONFLICTS_WITH_SOFT_DELETED_ENTRIES: 691 decodedPreventSoftDeletedConflicts = 692 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 693 break; 694 case TYPE_PRE_COMMIT_VALIDATION_LEVEL: 695 final int preCommitIntValue = 696 ASN1Enumerated.decodeAsEnumerated(e).intValue(); 697 decodedPreCommitLevel = 698 UniquenessValidationLevel.valueOf(preCommitIntValue); 699 if (decodedPreCommitLevel == null) 700 { 701 throw new LDAPException(ResultCode.DECODING_ERROR, 702 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_PRE_COMMIT_LEVEL.get( 703 preCommitIntValue)); 704 } 705 break; 706 case TYPE_POST_COMMIT_VALIDATION_LEVEL: 707 final int postCommitIntValue = 708 ASN1Enumerated.decodeAsEnumerated(e).intValue(); 709 decodedPostCommitLevel = 710 UniquenessValidationLevel.valueOf(postCommitIntValue); 711 if (decodedPostCommitLevel == null) 712 { 713 throw new LDAPException(ResultCode.DECODING_ERROR, 714 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_POST_COMMIT_LEVEL.get( 715 postCommitIntValue)); 716 } 717 break; 718 case TYPE_ALERT_ON_POST_VALIDATION_CONFLICT_DETECTION: 719 decodedAlertOnPostCommitConflictDetection = 720 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 721 break; 722 case TYPE_CREATE_CONFLICT_PREVENTION_DETAILS_ENTRY: 723 decodedCreateConflictPreventionDetailsEntry = 724 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 725 break; 726 default: 727 throw new LDAPException(ResultCode.DECODING_ERROR, 728 ERR_UNIQUENESS_REQ_DECODE_UNKNOWN_ELEMENT_TYPE.get( 729 StaticUtils.toHex(e.getType()))); 730 } 731 } 732 733 if (decodedUniquenessID == null) 734 { 735 throw new LDAPException(ResultCode.DECODING_ERROR, 736 ERR_UNIQUENESS_REQ_MISSING_UNIQUENESS_ID.get()); 737 } 738 739 if (decodedAttributeTypes.isEmpty() && (decodedFilter == null)) 740 { 741 throw new LDAPException(ResultCode.DECODING_ERROR, 742 ERR_UNIQUENESS_REQ_NO_ATTRS_OR_FILTER.get()); 743 } 744 745 uniquenessID = decodedUniquenessID; 746 attributeTypes = decodedAttributeTypes; 747 multipleAttributeBehavior = decodedMultipleAttributeBehavior; 748 baseDN = decodedBaseDN; 749 filter = decodedFilter; 750 preventConflictsWithSoftDeletedEntries = 751 decodedPreventSoftDeletedConflicts; 752 preCommitValidationLevel = decodedPreCommitLevel; 753 postCommitValidationLevel = decodedPostCommitLevel; 754 alertOnPostCommitConflictDetection = 755 decodedAlertOnPostCommitConflictDetection; 756 createConflictPreventionDetailsEntry = 757 decodedCreateConflictPreventionDetailsEntry; 758 } 759 catch (final LDAPException le) 760 { 761 Debug.debugException(le); 762 throw le; 763 } 764 catch (final Exception e) 765 { 766 Debug.debugException(e); 767 throw new LDAPException(ResultCode.DECODING_ERROR, 768 ERR_UNIQUENESS_REQ_DECODE_ERROR_DECODING_VALUE.get( 769 StaticUtils.getExceptionMessage(e)), 770 e); 771 } 772 } 773 774 775 776 /** 777 * Retrieves the uniqueness identifier for this control, which may be used to 778 * identify the response control that corresponds to this request control. 779 * This is primarily useful for requests that contain multiple uniqueness 780 * controls, as there may be a separate response control for each. 781 * 782 * @return The uniqueness identifier for this control. 783 */ 784 @NotNull() 785 public String getUniquenessID() 786 { 787 return uniquenessID; 788 } 789 790 791 792 /** 793 * Retrieves the set of attribute types that the server will check for 794 * uniqueness conflicts. 795 * 796 * @return The set of attribute types that the server will check for 797 * uniqueness conflicts, or an empty set if only a filter should be 798 * used to identify conflicts. 799 */ 800 @NotNull() 801 public Set<String> getAttributeTypes() 802 { 803 return attributeTypes; 804 } 805 806 807 808 /** 809 * Retrieves the behavior that the server should exhibit if multiple attribute 810 * types are configured. 811 * 812 * @return The behavior that the server should exhibit if multiple attribute 813 * types are configured. 814 */ 815 @NotNull() 816 public UniquenessMultipleAttributeBehavior getMultipleAttributeBehavior() 817 { 818 return multipleAttributeBehavior; 819 } 820 821 822 823 /** 824 * Retrieves the base DN that will be used for searches used to identify 825 * uniqueness conflicts, if defined. 826 * 827 * @return The base DN that will be used for searches used to identify 828 * uniqueness conflicts, or {@code null} if the server should search 829 * below all public naming contexts. 830 */ 831 @Nullable() 832 public String getBaseDN() 833 { 834 return baseDN; 835 } 836 837 838 839 /** 840 * Retrieves a filter that will be used to identify uniqueness conflicts, if 841 * defined. 842 * 843 * @return A filter that will be used to identify uniqueness conflicts, or 844 * {@code null} if no filter has been defined. 845 */ 846 @Nullable() 847 public Filter getFilter() 848 { 849 return filter; 850 } 851 852 853 854 /** 855 * Indicates whether the server should attempt to identify conflicts with 856 * soft-deleted entries. 857 * 858 * @return {@code true} if the server should identify conflicts with both 859 * regular entries and soft-deleted entries, or {@code false} if the 860 * server should only identify conflicts with regular entries. 861 */ 862 public boolean preventConflictsWithSoftDeletedEntries() 863 { 864 return preventConflictsWithSoftDeletedEntries; 865 } 866 867 868 869 /** 870 * Retrieves the pre-commit validation level, which will be used to identify 871 * any conflicts before the associated request is processed. 872 * 873 * @return The pre-commit validation level. 874 */ 875 @NotNull() 876 public UniquenessValidationLevel getPreCommitValidationLevel() 877 { 878 return preCommitValidationLevel; 879 } 880 881 882 883 /** 884 * Retrieves the post-commit validation level, which will be used to identify 885 * any conflicts that were introduced by the request with which the control is 886 * associated, or by some other concurrent changed processed in the server. 887 * 888 * @return The post-commit validation level. 889 */ 890 @NotNull() 891 public UniquenessValidationLevel getPostCommitValidationLevel() 892 { 893 return postCommitValidationLevel; 894 } 895 896 897 898 /** 899 * Indicates whether the server should raise an administrative alert if a 900 * conflict is detected during post-commit validation processing. 901 * 902 * @return {@code true} if the server should raise an administrative alert if 903 * a conflict is detected during post-commit validation processing, 904 * or {@code false} if not. 905 */ 906 public boolean alertOnPostCommitConflictDetection() 907 { 908 return alertOnPostCommitConflictDetection; 909 } 910 911 912 913 /** 914 * Indicates whether the server should create a temporary conflict prevention 915 * details entry before beginning pre-commit validation to provide better 916 * support for preventing conflicts. If created, the entry will be removed 917 * after post-commit validation processing has completed. 918 * 919 * @return {@code true} if the server should create a temporary conflict 920 * prevention details entry before beginning pre-commit validation, 921 * or {@code false} if not. 922 */ 923 public boolean createConflictPreventionDetailsEntry() 924 { 925 return createConflictPreventionDetailsEntry; 926 } 927 928 929 930 /** 931 * {@inheritDoc} 932 */ 933 @Override() 934 @NotNull() 935 public String getControlName() 936 { 937 return INFO_UNIQUENESS_REQ_CONTROL_NAME.get(); 938 } 939 940 941 942 /** 943 * {@inheritDoc} 944 */ 945 @Override() 946 public void toString(@NotNull final StringBuilder buffer) 947 { 948 buffer.append("UniquenessRequestControl(isCritical="); 949 buffer.append(isCritical()); 950 buffer.append(", uniquenessID='"); 951 buffer.append(uniquenessID); 952 buffer.append("', attributeTypes={"); 953 954 final Iterator<String> attributeTypesIterator = attributeTypes.iterator(); 955 while (attributeTypesIterator.hasNext()) 956 { 957 buffer.append('\''); 958 buffer.append(attributeTypesIterator.next()); 959 buffer.append('\''); 960 961 if (attributeTypesIterator.hasNext()) 962 { 963 buffer.append(", "); 964 } 965 } 966 967 buffer.append("}, multipleAttributeBehavior="); 968 buffer.append(multipleAttributeBehavior); 969 970 if (baseDN != null) 971 { 972 buffer.append(", baseDN='"); 973 buffer.append(baseDN); 974 buffer.append('\''); 975 } 976 977 if (filter != null) 978 { 979 buffer.append(", filter='"); 980 buffer.append(filter); 981 buffer.append('\''); 982 } 983 984 buffer.append(", preventConflictsWithSoftDeletedEntries="); 985 buffer.append(preventConflictsWithSoftDeletedEntries); 986 buffer.append(", preCommitValidationLevel="); 987 buffer.append(preCommitValidationLevel); 988 buffer.append(", postCommitValidationLevel="); 989 buffer.append(postCommitValidationLevel); 990 buffer.append(", alertOnPostCommitConflictDetection="); 991 buffer.append(alertOnPostCommitConflictDetection); 992 buffer.append(", createConflictPreventionDetailsEntry="); 993 buffer.append(createConflictPreventionDetailsEntry); 994 buffer.append(')'); 995 } 996}