001/* 002 * Copyright 2009-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-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) 2009-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.io.Serializable; 041import java.util.ArrayList; 042import java.util.List; 043 044import com.unboundid.asn1.ASN1Boolean; 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.asn1.ASN1Sequence; 048import com.unboundid.asn1.ASN1Set; 049import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.ResultCode; 052import com.unboundid.util.Debug; 053import com.unboundid.util.NotMutable; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.StaticUtils; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059import com.unboundid.util.Validator; 060import com.unboundid.util.json.JSONArray; 061import com.unboundid.util.json.JSONField; 062import com.unboundid.util.json.JSONObject; 063import com.unboundid.util.json.JSONValue; 064 065import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 066 067 068 069/** 070 * This class provides an implementation of a join rule as used by the LDAP join 071 * request control. See the class-level documentation for the 072 * {@link JoinRequestControl} class for additional information and an example 073 * demonstrating its use. 074 * <BR> 075 * <BLOCKQUOTE> 076 * <B>NOTE:</B> This class, and other classes within the 077 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 078 * supported for use against Ping Identity, UnboundID, and 079 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 080 * for proprietary functionality or for external specifications that are not 081 * considered stable or mature enough to be guaranteed to work in an 082 * interoperable way with other types of LDAP servers. 083 * </BLOCKQUOTE> 084 * <BR> 085 * Join rules are encoded as follows: 086 * <PRE> 087 * JoinRule ::= CHOICE { 088 * and [0] SET (1 .. MAX) of JoinRule, 089 * or [1] SET (1 .. MAX) of JoinRule, 090 * dnJoin [2] AttributeDescription, 091 * equalityJoin [3] JoinRuleAssertion, 092 * containsJoin [4] JoinRuleAssertion, 093 * reverseDNJoin [5] AttributeDescription, 094 * ... } 095 * 096 * JoinRuleAssertion ::= SEQUENCE { 097 * sourceAttribute AttributeDescription, 098 * targetAttribute AttributeDescription, 099 * matchAll BOOLEAN DEFAULT FALSE } 100 * </PRE> 101 */ 102@NotMutable() 103@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 104public final class JoinRule 105 implements Serializable 106{ 107 /** 108 * The join rule type that will be used for AND join rules. 109 */ 110 public static final byte JOIN_TYPE_AND = (byte) 0xA0; 111 112 113 114 /** 115 * The join rule type that will be used for OR join rules. 116 */ 117 public static final byte JOIN_TYPE_OR = (byte) 0xA1; 118 119 120 121 /** 122 * The join rule type that will be used for DN join rules. 123 */ 124 public static final byte JOIN_TYPE_DN = (byte) 0x82; 125 126 127 128 /** 129 * The join rule type that will be used for equality join rules. 130 */ 131 public static final byte JOIN_TYPE_EQUALITY = (byte) 0xA3; 132 133 134 135 /** 136 * The join rule type that will be used for contains join rules. 137 */ 138 public static final byte JOIN_TYPE_CONTAINS = (byte) 0xA4; 139 140 141 142 /** 143 * The join rule type that will be used for reverse DN join rules. 144 */ 145 public static final byte JOIN_TYPE_REVERSE_DN = (byte) 0x85; 146 147 148 149 /** 150 * The name of the field used to indicate whether to match all source 151 * attribute values in the JSON representation of this join rule. 152 */ 153 @NotNull private static final String JSON_FIELD_MATCH_ALL = "match-all"; 154 155 156 157 /** 158 * The name of the field used to hold the nested join rules in the JSON 159 * representation of this join rule. 160 */ 161 @NotNull private static final String JSON_FIELD_RULES = "rules"; 162 163 164 165 /** 166 * The name of the field used to hold the name of a source entry attribute in 167 * the JSON representation of this join rule. 168 */ 169 @NotNull private static final String JSON_FIELD_SOURCE_ATTRIBUTE = 170 "source-attribute"; 171 172 173 174 /** 175 * The name of the field used to hold the name of a target entry attribute in 176 * the JSON representation of this join rule. 177 */ 178 @NotNull private static final String JSON_FIELD_TARGET_ATTRIBUTE = 179 "target-attribute"; 180 181 182 183 /** 184 * The name of the field used to hold the join rule type in the JSON 185 * representation of this join rule. 186 */ 187 @NotNull private static final String JSON_FIELD_TYPE = "type"; 188 189 190 191 /** 192 * The string that should be used to represent the AND join rule type in JSON 193 * object representations. 194 */ 195 @NotNull private static final String JSON_TYPE_AND = "and"; 196 197 198 199 /** 200 * The string that should be used to represent the contains join rule type in 201 * JSON object representations. 202 */ 203 @NotNull private static final String JSON_TYPE_CONTAINS = "contains"; 204 205 206 207 /** 208 * The string that should be used to represent the DN join rule type in JSON 209 * object representations. 210 */ 211 @NotNull private static final String JSON_TYPE_DN = "dn"; 212 213 214 215 /** 216 * The string that should be used to represent the equality join rule type in 217 * JSON object representations. 218 */ 219 @NotNull private static final String JSON_TYPE_EQUALITY = "equality"; 220 221 222 223 /** 224 * The string that should be used to represent the OR join rule type in JSON 225 * object representations. 226 */ 227 @NotNull private static final String JSON_TYPE_OR = "or"; 228 229 230 231 /** 232 * The string that should be used to represent the reverse DN join rule type 233 * in JSON object representations. 234 */ 235 @NotNull private static final String JSON_TYPE_REVERSE_DN = "reverse-dn"; 236 237 238 239 /** 240 * An empty array of join rules that will be used as the set of components 241 * for DN and equality join rules. 242 */ 243 @NotNull private static final JoinRule[] NO_RULES = new JoinRule[0]; 244 245 246 247 /** 248 * The serial version UID for this serializable class. 249 */ 250 private static final long serialVersionUID = 9041070342511946580L; 251 252 253 254 // Indicates whether all values of a multivalued source attribute must be 255 // present in the target entry for it to be considered a match. 256 private final boolean matchAll; 257 258 // The BER type for this join rule. 259 private final byte type; 260 261 // The set of subordinate components for this join rule. 262 @NotNull private final JoinRule[] components; 263 264 // The name of the source attribute for this join rule. 265 @Nullable private final String sourceAttribute; 266 267 // The name of the target attribute for this join rule. 268 @Nullable private final String targetAttribute; 269 270 271 272 /** 273 * Creates a new join rule with the provided information. 274 * 275 * @param type The BER type for this join rule. 276 * @param components The set of subordinate components for this join 277 * rule. 278 * @param sourceAttribute The name of the source attribute for this join 279 * rule. 280 * @param targetAttribute The name of the target attribute for this join 281 * rule. 282 * @param matchAll Indicates whether all values of a multivalued 283 * source attribute must be present in the target 284 * entry for it to be considered a match. 285 */ 286 private JoinRule(final byte type, @NotNull final JoinRule[] components, 287 @Nullable final String sourceAttribute, 288 @Nullable final String targetAttribute, 289 final boolean matchAll) 290 { 291 this.type = type; 292 this.components = components; 293 this.sourceAttribute = sourceAttribute; 294 this.targetAttribute = targetAttribute; 295 this.matchAll = matchAll; 296 } 297 298 299 300 /** 301 * Creates an AND join rule in which all of the contained join rules must 302 * match an entry for it to be included in the join. 303 * 304 * @param components The set of components to include in this join. It must 305 * not be {@code null} or empty. 306 * 307 * @return The created AND join rule. 308 */ 309 @NotNull() 310 public static JoinRule createANDRule(@NotNull final JoinRule... components) 311 { 312 Validator.ensureNotNull(components); 313 Validator.ensureFalse(components.length == 0); 314 315 return new JoinRule(JOIN_TYPE_AND, components, null, null, false); 316 } 317 318 319 320 /** 321 * Creates an AND join rule in which all of the contained join rules must 322 * match an entry for it to be included in the join. 323 * 324 * @param components The set of components to include in this join. It must 325 * not be {@code null} or empty. 326 * 327 * @return The created AND join rule. 328 */ 329 @NotNull() 330 public static JoinRule createANDRule(@NotNull final List<JoinRule> components) 331 { 332 Validator.ensureNotNull(components); 333 Validator.ensureFalse(components.isEmpty()); 334 335 final JoinRule[] compArray = new JoinRule[components.size()]; 336 return new JoinRule(JOIN_TYPE_AND, components.toArray(compArray), null, 337 null, false); 338 } 339 340 341 342 /** 343 * Creates an OR join rule in which at least one of the contained join rules 344 * must match an entry for it to be included in the join. 345 * 346 * @param components The set of components to include in this join. It must 347 * not be {@code null} or empty. 348 * 349 * @return The created OR join rule. 350 */ 351 @NotNull() 352 public static JoinRule createORRule(@NotNull final JoinRule... components) 353 { 354 Validator.ensureNotNull(components); 355 Validator.ensureFalse(components.length == 0); 356 357 return new JoinRule(JOIN_TYPE_OR, components, null, null, false); 358 } 359 360 361 362 /** 363 * Creates an OR join rule in which at least one of the contained join rules 364 * must match an entry for it to be included in the join. 365 * 366 * @param components The set of components to include in this join. It must 367 * not be {@code null} or empty. 368 * 369 * @return The created OR join rule. 370 */ 371 @NotNull() 372 public static JoinRule createORRule(@NotNull final List<JoinRule> components) 373 { 374 Validator.ensureNotNull(components); 375 Validator.ensureFalse(components.isEmpty()); 376 377 final JoinRule[] compArray = new JoinRule[components.size()]; 378 return new JoinRule(JOIN_TYPE_OR, components.toArray(compArray), null, 379 null, false); 380 } 381 382 383 384 /** 385 * Creates a DN join rule in which the value(s) of the source attribute must 386 * specify the DN(s) of the target entries to include in the join. 387 * 388 * @param sourceAttribute The name or OID of the attribute in the source 389 * entry whose values contain the DNs of the entries 390 * to be included in the join. It must not be 391 * {@code null}, and it must be associated with a 392 * distinguished name or name and optional UID 393 * syntax. 394 * 395 * @return The created DN join rule. 396 */ 397 @NotNull() 398 public static JoinRule createDNJoin(@NotNull final String sourceAttribute) 399 { 400 Validator.ensureNotNull(sourceAttribute); 401 402 return new JoinRule(JOIN_TYPE_DN, NO_RULES, sourceAttribute, null, false); 403 } 404 405 406 407 /** 408 * Creates an equality join rule in which the value(s) of the source attribute 409 * in the source entry must be equal to the value(s) of the target attribute 410 * of a target entry for it to be included in the join. 411 * 412 * @param sourceAttribute The name or OID of the attribute in the source 413 * entry whose value(s) should be matched in target 414 * entries to be included in the join. It must not 415 * be {@code null}. 416 * @param targetAttribute The name or OID of the attribute whose value(s) 417 * must match the source value(s) in entries included 418 * in the join. It must not be {@code null}. 419 * @param matchAll Indicates whether all values of a multivalued 420 * source attribute must be present in the target 421 * entry for it to be considered a match. 422 * 423 * @return The created equality join rule. 424 */ 425 @NotNull() 426 public static JoinRule createEqualityJoin( 427 @NotNull final String sourceAttribute, 428 @NotNull final String targetAttribute, 429 final boolean matchAll) 430 { 431 Validator.ensureNotNull(sourceAttribute, targetAttribute); 432 433 return new JoinRule(JOIN_TYPE_EQUALITY, NO_RULES, sourceAttribute, 434 targetAttribute, matchAll); 435 } 436 437 438 439 /** 440 * Creates an equality join rule in which the value(s) of the source attribute 441 * in the source entry must be equal to or a substring of the value(s) of the 442 * target attribute of a target entry for it to be included in the join. 443 * 444 * @param sourceAttribute The name or OID of the attribute in the source 445 * entry whose value(s) should be matched in target 446 * entries to be included in the join. It must not 447 * be {@code null}. 448 * @param targetAttribute The name or OID of the attribute whose value(s) 449 * must equal or contain the source value(s) in 450 * entries included in the join. It must not be 451 * {@code null}. 452 * @param matchAll Indicates whether all values of a multivalued 453 * source attribute must be present in the target 454 * entry for it to be considered a match. 455 * 456 * @return The created equality join rule. 457 */ 458 @NotNull() 459 public static JoinRule createContainsJoin( 460 @NotNull final String sourceAttribute, 461 @NotNull final String targetAttribute, 462 final boolean matchAll) 463 { 464 Validator.ensureNotNull(sourceAttribute, targetAttribute); 465 466 return new JoinRule(JOIN_TYPE_CONTAINS, NO_RULES, sourceAttribute, 467 targetAttribute, matchAll); 468 } 469 470 471 472 /** 473 * Creates a reverse DN join rule in which the target entries to include in 474 * the join must include a specified attribute that contains the DN of the 475 * source entry. 476 * 477 * @param targetAttribute The name or OID of the attribute in the target 478 * entries which must contain the DN of the source 479 * entry. It must not be {@code null}, and it must 480 * be associated with a distinguished nme or name and 481 * optional UID syntax. 482 * 483 * @return The created reverse DN join rule. 484 */ 485 @NotNull() 486 public static JoinRule createReverseDNJoin( 487 @NotNull final String targetAttribute) 488 { 489 Validator.ensureNotNull(targetAttribute); 490 491 return new JoinRule(JOIN_TYPE_REVERSE_DN, NO_RULES, null, targetAttribute, 492 false); 493 } 494 495 496 497 /** 498 * Retrieves the join rule type for this join rule. 499 * 500 * @return The join rule type for this join rule. 501 */ 502 public byte getType() 503 { 504 return type; 505 } 506 507 508 509 /** 510 * Retrieves the set of subordinate components for this AND or OR join rule. 511 * 512 * @return The set of subordinate components for this AND or OR join rule, or 513 * an empty list if this is not an AND or OR join rule. 514 */ 515 @NotNull() 516 public JoinRule[] getComponents() 517 { 518 return components; 519 } 520 521 522 523 /** 524 * Retrieves the name of the source attribute for this DN, equality, or 525 * contains join rule. 526 * 527 * @return The name of the source attribute for this DN, equality, or 528 * contains join rule, or {@code null} if this is some other type of 529 * join rule. 530 */ 531 @Nullable() 532 public String getSourceAttribute() 533 { 534 return sourceAttribute; 535 } 536 537 538 539 /** 540 * Retrieves the name of the target attribute for this reverse DN, equality, 541 * or contains join rule. 542 * 543 * @return The name of the target attribute for this reverse DN, equality, or 544 * contains join rule, or {@code null} if this is some other type of 545 * join rule. 546 */ 547 @Nullable() 548 public String getTargetAttribute() 549 { 550 return targetAttribute; 551 } 552 553 554 555 /** 556 * Indicates whether all values of a multivalued source attribute must be 557 * present in a target entry for it to be considered a match. The return 558 * value will only be meaningful for equality join rules. 559 * 560 * @return {@code true} if all values of the source attribute must be 561 * included in the target attribute of an entry for it to be 562 * considered for inclusion in the join, or {@code false} if it is 563 * only necessary for at least one of the values to be included in a 564 * target entry for it to be considered for inclusion in the join. 565 */ 566 public boolean matchAll() 567 { 568 return matchAll; 569 } 570 571 572 573 /** 574 * Encodes this join rule as appropriate for inclusion in an LDAP join 575 * request control. 576 * 577 * @return The encoded representation of this join rule. 578 */ 579 @NotNull() 580 ASN1Element encode() 581 { 582 switch (type) 583 { 584 case JOIN_TYPE_AND: 585 case JOIN_TYPE_OR: 586 final ASN1Element[] compElements = new ASN1Element[components.length]; 587 for (int i=0; i < components.length; i++) 588 { 589 compElements[i] = components[i].encode(); 590 } 591 return new ASN1Set(type, compElements); 592 593 case JOIN_TYPE_DN: 594 return new ASN1OctetString(type, sourceAttribute); 595 596 case JOIN_TYPE_EQUALITY: 597 case JOIN_TYPE_CONTAINS: 598 if (matchAll) 599 { 600 return new ASN1Sequence(type, 601 new ASN1OctetString(sourceAttribute), 602 new ASN1OctetString(targetAttribute), 603 new ASN1Boolean(matchAll)); 604 } 605 else 606 { 607 return new ASN1Sequence(type, 608 new ASN1OctetString(sourceAttribute), 609 new ASN1OctetString(targetAttribute)); 610 } 611 case JOIN_TYPE_REVERSE_DN: 612 return new ASN1OctetString(type, targetAttribute); 613 614 default: 615 // This should never happen. 616 return null; 617 } 618 } 619 620 621 622 /** 623 * Decodes the provided ASN.1 element as a join rule. 624 * 625 * @param element The element to be decoded. 626 * 627 * @return The decoded join rule. 628 * 629 * @throws LDAPException If a problem occurs while attempting to decode the 630 * provided element as a join rule. 631 */ 632 @NotNull() 633 static JoinRule decode(@NotNull final ASN1Element element) 634 throws LDAPException 635 { 636 final byte elementType = element.getType(); 637 switch (elementType) 638 { 639 case JOIN_TYPE_AND: 640 case JOIN_TYPE_OR: 641 try 642 { 643 final ASN1Element[] elements = 644 ASN1Set.decodeAsSet(element).elements(); 645 final JoinRule[] rules = new JoinRule[elements.length]; 646 for (int i=0; i < rules.length; i++) 647 { 648 rules[i] = decode(elements[i]); 649 } 650 651 return new JoinRule(elementType, rules, null, null, false); 652 } 653 catch (final Exception e) 654 { 655 Debug.debugException(e); 656 657 throw new LDAPException(ResultCode.DECODING_ERROR, 658 ERR_JOIN_RULE_CANNOT_DECODE.get( 659 StaticUtils.getExceptionMessage(e)), 660 e); 661 } 662 663 664 case JOIN_TYPE_DN: 665 return new JoinRule(elementType, NO_RULES, 666 ASN1OctetString.decodeAsOctetString(element).stringValue(), null, 667 false); 668 669 670 case JOIN_TYPE_EQUALITY: 671 case JOIN_TYPE_CONTAINS: 672 try 673 { 674 final ASN1Element[] elements = 675 ASN1Sequence.decodeAsSequence(element).elements(); 676 677 final String sourceAttribute = 678 elements[0].decodeAsOctetString().stringValue(); 679 final String targetAttribute = 680 elements[1].decodeAsOctetString().stringValue(); 681 682 boolean matchAll = false; 683 if (elements.length == 3) 684 { 685 matchAll = elements[2].decodeAsBoolean().booleanValue(); 686 } 687 688 return new JoinRule(elementType, NO_RULES, sourceAttribute, 689 targetAttribute, matchAll); 690 } 691 catch (final Exception e) 692 { 693 Debug.debugException(e); 694 695 throw new LDAPException(ResultCode.DECODING_ERROR, 696 ERR_JOIN_RULE_CANNOT_DECODE.get( 697 StaticUtils.getExceptionMessage(e)), 698 e); 699 } 700 701 702 case JOIN_TYPE_REVERSE_DN: 703 return new JoinRule(elementType, NO_RULES, null, 704 ASN1OctetString.decodeAsOctetString(element).stringValue(), false); 705 706 707 default: 708 throw new LDAPException(ResultCode.DECODING_ERROR, 709 ERR_JOIN_RULE_DECODE_INVALID_TYPE.get( 710 StaticUtils.toHex(elementType))); 711 } 712 } 713 714 715 716 /** 717 * Retrieve a JSON object representation of this join rule. 718 * 719 * @return A JSON object representation of this join rule. 720 */ 721 @NotNull() 722 public JSONObject toJSON() 723 { 724 switch (type) 725 { 726 case JOIN_TYPE_DN: 727 return new JSONObject( 728 new JSONField(JSON_FIELD_TYPE, JSON_TYPE_DN), 729 new JSONField(JSON_FIELD_SOURCE_ATTRIBUTE, sourceAttribute)); 730 731 case JOIN_TYPE_REVERSE_DN: 732 return new JSONObject( 733 new JSONField(JSON_FIELD_TYPE, JSON_TYPE_REVERSE_DN), 734 new JSONField(JSON_FIELD_TARGET_ATTRIBUTE, targetAttribute)); 735 736 case JOIN_TYPE_EQUALITY: 737 return new JSONObject( 738 new JSONField(JSON_FIELD_TYPE, JSON_TYPE_EQUALITY), 739 new JSONField(JSON_FIELD_SOURCE_ATTRIBUTE, sourceAttribute), 740 new JSONField(JSON_FIELD_TARGET_ATTRIBUTE, targetAttribute), 741 new JSONField(JSON_FIELD_MATCH_ALL, matchAll)); 742 743 case JOIN_TYPE_CONTAINS: 744 return new JSONObject( 745 new JSONField(JSON_FIELD_TYPE, JSON_TYPE_CONTAINS), 746 new JSONField(JSON_FIELD_SOURCE_ATTRIBUTE, sourceAttribute), 747 new JSONField(JSON_FIELD_TARGET_ATTRIBUTE, targetAttribute), 748 new JSONField(JSON_FIELD_MATCH_ALL, matchAll)); 749 750 case JOIN_TYPE_AND: 751 final List<JSONValue> andRuleValues = 752 new ArrayList<>(components.length); 753 for (final JoinRule rule : components) 754 { 755 andRuleValues.add(rule.toJSON()); 756 } 757 758 return new JSONObject( 759 new JSONField(JSON_FIELD_TYPE, JSON_TYPE_AND), 760 new JSONField(JSON_FIELD_RULES, new JSONArray(andRuleValues))); 761 762 case JOIN_TYPE_OR: 763 final List<JSONValue> orRuleValues = 764 new ArrayList<>(components.length); 765 for (final JoinRule rule : components) 766 { 767 orRuleValues.add(rule.toJSON()); 768 } 769 770 return new JSONObject( 771 new JSONField(JSON_FIELD_TYPE, JSON_TYPE_OR), 772 new JSONField(JSON_FIELD_RULES, new JSONArray(orRuleValues))); 773 774 default: 775 // This should never happen. 776 return null; 777 } 778 } 779 780 781 782 /** 783 * Decodes the provided JSON object as a join rule. 784 * 785 * @param o The JSON object that represents the join rule to decode. 786 * It must not be {@code null}. 787 * @param strict Indicates whether to use strict mode when decoding the 788 * provided JSON object. If this is {@code true}, then this 789 * method will throw an exception if the provided JSON object 790 * contains any unrecognized fields. If this is 791 * {@code false}, then unrecognized fields will be ignored. 792 * 793 * @return The join rule decoded from the provided JSON object. 794 * 795 * @throws LDAPException If the provided JSON object cannot be decoded as a 796 * valid join rule. 797 */ 798 @NotNull() 799 public static JoinRule decodeJSONJoinRule(@NotNull final JSONObject o, 800 final boolean strict) 801 throws LDAPException 802 { 803 final String type = o.getFieldAsString(JSON_FIELD_TYPE); 804 if (type == null) 805 { 806 throw new LDAPException(ResultCode.DECODING_ERROR, 807 ERR_JOIN_RULE_JSON_MISSING_TYPE.get(JSON_FIELD_TYPE)); 808 } 809 810 switch (type) 811 { 812 case JSON_TYPE_DN: 813 return decodeJSONDNJoinRule(o, strict); 814 815 case JSON_TYPE_REVERSE_DN: 816 return decodeJSONReverseDNJoinRule(o, strict); 817 818 case JSON_TYPE_EQUALITY: 819 return decodeJSONEqualityJoinRule(o, strict); 820 821 case JSON_TYPE_CONTAINS: 822 return decodeJSONContainsJoinRule(o, strict); 823 824 case JSON_TYPE_AND: 825 return decodeJSONANDJoinRule(o, strict); 826 827 case JSON_TYPE_OR: 828 return decodeJSONORJoinRule(o, strict); 829 830 default: 831 throw new LDAPException(ResultCode.DECODING_ERROR, 832 ERR_JOIN_RULE_JSON_UNRECOGNIZED_TYPE.get(type)); 833 } 834 } 835 836 837 838 /** 839 * Decodes the provided JSON object as a DN join rule. 840 * 841 * @param o The JSON object that represents the join rule to decode. 842 * It must not be {@code null}. 843 * @param strict Indicates whether to use strict mode when decoding the 844 * provided JSON object. If this is {@code true}, then this 845 * method will throw an exception if the provided JSON object 846 * contains any unrecognized fields. If this is 847 * {@code false}, then unrecognized fields will be ignored. 848 * 849 * @return The DN join rule decoded from the provided JSON object. 850 * 851 * @throws LDAPException If the provided JSON object cannot be decoded as a 852 * valid DN join rule. 853 */ 854 @NotNull() 855 private static JoinRule decodeJSONDNJoinRule(@NotNull final JSONObject o, 856 final boolean strict) 857 throws LDAPException 858 { 859 final String sourceAttribute = 860 o.getFieldAsString(JSON_FIELD_SOURCE_ATTRIBUTE); 861 if (sourceAttribute == null) 862 { 863 throw new LDAPException(ResultCode.DECODING_ERROR, 864 ERR_JOIN_RULE_JSON_DN_MISSING_SOURCE_ATTR.get( 865 JSON_FIELD_SOURCE_ATTRIBUTE)); 866 } 867 868 if (strict) 869 { 870 final List<String> unrecognizedFields = 871 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 872 o, JSON_FIELD_TYPE, JSON_FIELD_SOURCE_ATTRIBUTE); 873 if (! unrecognizedFields.isEmpty()) 874 { 875 throw new LDAPException(ResultCode.DECODING_ERROR, 876 ERR_JOIN_RULE_JSON_UNRECOGNIZED_DN_FIELD.get( 877 unrecognizedFields.get(0))); 878 } 879 } 880 881 return createDNJoin(sourceAttribute); 882 } 883 884 885 886 /** 887 * Decodes the provided JSON object as a reverse DN join rule. 888 * 889 * @param o The JSON object that represents the join rule to decode. 890 * It must not be {@code null}. 891 * @param strict Indicates whether to use strict mode when decoding the 892 * provided JSON object. If this is {@code true}, then this 893 * method will throw an exception if the provided JSON object 894 * contains any unrecognized fields. If this is 895 * {@code false}, then unrecognized fields will be ignored. 896 * 897 * @return The reverse DN join rule decoded from the provided JSON object. 898 * 899 * @throws LDAPException If the provided JSON object cannot be decoded as a 900 * valid reverse DN join rule. 901 */ 902 @NotNull() 903 private static JoinRule decodeJSONReverseDNJoinRule( 904 @NotNull final JSONObject o, 905 final boolean strict) 906 throws LDAPException 907 { 908 final String targetAttribute = 909 o.getFieldAsString(JSON_FIELD_TARGET_ATTRIBUTE); 910 if (targetAttribute == null) 911 { 912 throw new LDAPException(ResultCode.DECODING_ERROR, 913 ERR_JOIN_RULE_JSON_REVERSE_DN_MISSING_TARGET_ATTR.get( 914 JSON_FIELD_TARGET_ATTRIBUTE)); 915 } 916 917 if (strict) 918 { 919 final List<String> unrecognizedFields = 920 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 921 o, JSON_FIELD_TYPE, JSON_FIELD_TARGET_ATTRIBUTE); 922 if (! unrecognizedFields.isEmpty()) 923 { 924 throw new LDAPException(ResultCode.DECODING_ERROR, 925 ERR_JOIN_RULE_JSON_UNRECOGNIZED_REVERSE_DN_FIELD.get( 926 unrecognizedFields.get(0))); 927 } 928 } 929 930 return createReverseDNJoin(targetAttribute); 931 } 932 933 934 935 /** 936 * Decodes the provided JSON object as an equality join rule. 937 * 938 * @param o The JSON object that represents the join rule to decode. 939 * It must not be {@code null}. 940 * @param strict Indicates whether to use strict mode when decoding the 941 * provided JSON object. If this is {@code true}, then this 942 * method will throw an exception if the provided JSON object 943 * contains any unrecognized fields. If this is 944 * {@code false}, then unrecognized fields will be ignored. 945 * 946 * @return The equality join rule decoded from the provided JSON object. 947 * 948 * @throws LDAPException If the provided JSON object cannot be decoded as a 949 * valid equality join rule. 950 */ 951 @NotNull() 952 private static JoinRule decodeJSONEqualityJoinRule( 953 @NotNull final JSONObject o, 954 final boolean strict) 955 throws LDAPException 956 { 957 final String sourceAttribute = 958 o.getFieldAsString(JSON_FIELD_SOURCE_ATTRIBUTE); 959 if (sourceAttribute == null) 960 { 961 throw new LDAPException(ResultCode.DECODING_ERROR, 962 ERR_JOIN_RULE_JSON_EQUALITY_MISSING_FIELD.get( 963 JSON_FIELD_SOURCE_ATTRIBUTE)); 964 } 965 966 final String targetAttribute = 967 o.getFieldAsString(JSON_FIELD_TARGET_ATTRIBUTE); 968 if (targetAttribute == null) 969 { 970 throw new LDAPException(ResultCode.DECODING_ERROR, 971 ERR_JOIN_RULE_JSON_EQUALITY_MISSING_FIELD.get( 972 JSON_FIELD_TARGET_ATTRIBUTE)); 973 } 974 975 final Boolean matchAll = o.getFieldAsBoolean(JSON_FIELD_MATCH_ALL); 976 if (matchAll == null) 977 { 978 throw new LDAPException(ResultCode.DECODING_ERROR, 979 ERR_JOIN_RULE_JSON_EQUALITY_MISSING_FIELD.get(JSON_FIELD_MATCH_ALL)); 980 } 981 982 if (strict) 983 { 984 final List<String> unrecognizedFields = 985 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 986 o, JSON_FIELD_TYPE, JSON_FIELD_SOURCE_ATTRIBUTE, 987 JSON_FIELD_TARGET_ATTRIBUTE, JSON_FIELD_MATCH_ALL); 988 if (! unrecognizedFields.isEmpty()) 989 { 990 throw new LDAPException(ResultCode.DECODING_ERROR, 991 ERR_JOIN_RULE_JSON_UNRECOGNIZED_EQUALITY_FIELD.get( 992 unrecognizedFields.get(0))); 993 } 994 } 995 996 return createEqualityJoin(sourceAttribute, targetAttribute, matchAll); 997 } 998 999 1000 1001 /** 1002 * Decodes the provided JSON object as a contains join rule. 1003 * 1004 * @param o The JSON object that represents the join rule to decode. 1005 * It must not be {@code null}. 1006 * @param strict Indicates whether to use strict mode when decoding the 1007 * provided JSON object. If this is {@code true}, then this 1008 * method will throw an exception if the provided JSON object 1009 * contains any unrecognized fields. If this is 1010 * {@code false}, then unrecognized fields will be ignored. 1011 * 1012 * @return The contains join rule decoded from the provided JSON object. 1013 * 1014 * @throws LDAPException If the provided JSON object cannot be decoded as a 1015 * valid contains join rule. 1016 */ 1017 @NotNull() 1018 private static JoinRule decodeJSONContainsJoinRule( 1019 @NotNull final JSONObject o, 1020 final boolean strict) 1021 throws LDAPException 1022 { 1023 final String sourceAttribute = 1024 o.getFieldAsString(JSON_FIELD_SOURCE_ATTRIBUTE); 1025 if (sourceAttribute == null) 1026 { 1027 throw new LDAPException(ResultCode.DECODING_ERROR, 1028 ERR_JOIN_RULE_JSON_CONTAINS_MISSING_FIELD.get( 1029 JSON_FIELD_SOURCE_ATTRIBUTE)); 1030 } 1031 1032 final String targetAttribute = 1033 o.getFieldAsString(JSON_FIELD_TARGET_ATTRIBUTE); 1034 if (targetAttribute == null) 1035 { 1036 throw new LDAPException(ResultCode.DECODING_ERROR, 1037 ERR_JOIN_RULE_JSON_CONTAINS_MISSING_FIELD.get( 1038 JSON_FIELD_TARGET_ATTRIBUTE)); 1039 } 1040 1041 final Boolean matchAll = o.getFieldAsBoolean(JSON_FIELD_MATCH_ALL); 1042 if (matchAll == null) 1043 { 1044 throw new LDAPException(ResultCode.DECODING_ERROR, 1045 ERR_JOIN_RULE_JSON_CONTAINS_MISSING_FIELD.get(JSON_FIELD_MATCH_ALL)); 1046 } 1047 1048 if (strict) 1049 { 1050 final List<String> unrecognizedFields = 1051 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1052 o, JSON_FIELD_TYPE, JSON_FIELD_SOURCE_ATTRIBUTE, 1053 JSON_FIELD_TARGET_ATTRIBUTE, JSON_FIELD_MATCH_ALL); 1054 if (! unrecognizedFields.isEmpty()) 1055 { 1056 throw new LDAPException(ResultCode.DECODING_ERROR, 1057 ERR_JOIN_RULE_JSON_UNRECOGNIZED_CONTAINS_FIELD.get( 1058 unrecognizedFields.get(0))); 1059 } 1060 } 1061 1062 return createContainsJoin(sourceAttribute, targetAttribute, matchAll); 1063 } 1064 1065 1066 1067 /** 1068 * Decodes the provided JSON object as an AND join rule. 1069 * 1070 * @param o The JSON object that represents the join rule to decode. 1071 * It must not be {@code null}. 1072 * @param strict Indicates whether to use strict mode when decoding the 1073 * provided JSON object. If this is {@code true}, then this 1074 * method will throw an exception if the provided JSON object 1075 * contains any unrecognized fields. If this is 1076 * {@code false}, then unrecognized fields will be ignored. 1077 * 1078 * @return The AND join rule decoded from the provided JSON object. 1079 * 1080 * @throws LDAPException If the provided JSON object cannot be decoded as a 1081 * valid AND join rule. 1082 */ 1083 @NotNull() 1084 private static JoinRule decodeJSONANDJoinRule( 1085 @NotNull final JSONObject o, 1086 final boolean strict) 1087 throws LDAPException 1088 { 1089 final List<JSONValue> ruleValues = o.getFieldAsArray(JSON_FIELD_RULES); 1090 if (ruleValues == null) 1091 { 1092 throw new LDAPException(ResultCode.DECODING_ERROR, 1093 ERR_JOIN_RULE_JSON_AND_MISSING_RULES.get(JSON_FIELD_RULES)); 1094 } 1095 1096 if (ruleValues.isEmpty()) 1097 { 1098 throw new LDAPException(ResultCode.DECODING_ERROR, 1099 ERR_JOIN_RULE_JSON_AND_EMPTY_RULES.get(JSON_FIELD_RULES)); 1100 } 1101 1102 final List<JoinRule> rules = new ArrayList<>(ruleValues.size()); 1103 for (final JSONValue v : ruleValues) 1104 { 1105 if (v instanceof JSONObject) 1106 { 1107 rules.add(decodeJSONJoinRule((JSONObject) v, strict)); 1108 } 1109 else 1110 { 1111 throw new LDAPException(ResultCode.DECODING_ERROR, 1112 ERR_JOIN_RULE_JSON_AND_RULE_NOT_OBJECT.get(JSON_FIELD_RULES)); 1113 } 1114 } 1115 1116 if (strict) 1117 { 1118 final List<String> unrecognizedFields = 1119 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1120 o, JSON_FIELD_TYPE, JSON_FIELD_RULES); 1121 if (! unrecognizedFields.isEmpty()) 1122 { 1123 throw new LDAPException(ResultCode.DECODING_ERROR, 1124 ERR_JOIN_RULE_JSON_UNRECOGNIZED_AND_FIELD.get( 1125 unrecognizedFields.get(0))); 1126 } 1127 } 1128 1129 return createANDRule(rules); 1130 } 1131 1132 1133 1134 /** 1135 * Decodes the provided JSON object as an OR join rule. 1136 * 1137 * @param o The JSON object that represents the join rule to decode. 1138 * It must not be {@code null}. 1139 * @param strict Indicates whether to use strict mode when decoding the 1140 * provided JSON object. If this is {@code true}, then this 1141 * method will throw an exception if the provided JSON object 1142 * contains any unrecognized fields. If this is 1143 * {@code false}, then unrecognized fields will be ignored. 1144 * 1145 * @return The OR join rule decoded from the provided JSON object. 1146 * 1147 * @throws LDAPException If the provided JSON object cannot be decoded as a 1148 * valid OR join rule. 1149 */ 1150 @NotNull() 1151 private static JoinRule decodeJSONORJoinRule( 1152 @NotNull final JSONObject o, 1153 final boolean strict) 1154 throws LDAPException 1155 { 1156 final List<JSONValue> ruleValues = o.getFieldAsArray(JSON_FIELD_RULES); 1157 if (ruleValues == null) 1158 { 1159 throw new LDAPException(ResultCode.DECODING_ERROR, 1160 ERR_JOIN_RULE_JSON_OR_MISSING_RULES.get(JSON_FIELD_RULES)); 1161 } 1162 1163 if (ruleValues.isEmpty()) 1164 { 1165 throw new LDAPException(ResultCode.DECODING_ERROR, 1166 ERR_JOIN_RULE_JSON_OR_EMPTY_RULES.get(JSON_FIELD_RULES)); 1167 } 1168 1169 final List<JoinRule> rules = new ArrayList<>(ruleValues.size()); 1170 for (final JSONValue v : ruleValues) 1171 { 1172 if (v instanceof JSONObject) 1173 { 1174 rules.add(decodeJSONJoinRule((JSONObject) v, strict)); 1175 } 1176 else 1177 { 1178 throw new LDAPException(ResultCode.DECODING_ERROR, 1179 ERR_JOIN_RULE_JSON_OR_RULE_NOT_OBJECT.get(JSON_FIELD_RULES)); 1180 } 1181 } 1182 1183 if (strict) 1184 { 1185 final List<String> unrecognizedFields = 1186 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1187 o, JSON_FIELD_TYPE, JSON_FIELD_RULES); 1188 if (! unrecognizedFields.isEmpty()) 1189 { 1190 throw new LDAPException(ResultCode.DECODING_ERROR, 1191 ERR_JOIN_RULE_JSON_UNRECOGNIZED_OR_FIELD.get( 1192 unrecognizedFields.get(0))); 1193 } 1194 } 1195 1196 return createORRule(rules); 1197 } 1198 1199 1200 1201 /** 1202 * Retrieves a string representation of this join rule. 1203 * 1204 * @return A string representation of this join rule. 1205 */ 1206 @Override() 1207 @NotNull() 1208 public String toString() 1209 { 1210 final StringBuilder buffer = new StringBuilder(); 1211 toString(buffer); 1212 return buffer.toString(); 1213 } 1214 1215 1216 1217 /** 1218 * Appends a string representation of this join rule to the provided buffer. 1219 * 1220 * @param buffer The buffer to which the information should be appended. 1221 */ 1222 public void toString(@NotNull final StringBuilder buffer) 1223 { 1224 switch (type) 1225 { 1226 case JOIN_TYPE_AND: 1227 buffer.append("ANDJoinRule(components={"); 1228 for (int i=0; i < components.length; i++) 1229 { 1230 if (i > 0) 1231 { 1232 buffer.append(", "); 1233 } 1234 components[i].toString(buffer); 1235 } 1236 buffer.append("})"); 1237 break; 1238 1239 case JOIN_TYPE_OR: 1240 buffer.append("ORJoinRule(components={"); 1241 for (int i=0; i < components.length; i++) 1242 { 1243 if (i > 0) 1244 { 1245 buffer.append(", "); 1246 } 1247 components[i].toString(buffer); 1248 } 1249 buffer.append("})"); 1250 break; 1251 1252 case JOIN_TYPE_DN: 1253 buffer.append("DNJoinRule(sourceAttr="); 1254 buffer.append(sourceAttribute); 1255 buffer.append(')'); 1256 break; 1257 1258 case JOIN_TYPE_EQUALITY: 1259 buffer.append("EqualityJoinRule(sourceAttr="); 1260 buffer.append(sourceAttribute); 1261 buffer.append(", targetAttr="); 1262 buffer.append(targetAttribute); 1263 buffer.append(", matchAll="); 1264 buffer.append(matchAll); 1265 buffer.append(')'); 1266 break; 1267 1268 case JOIN_TYPE_CONTAINS: 1269 buffer.append("ContainsJoinRule(sourceAttr="); 1270 buffer.append(sourceAttribute); 1271 buffer.append(", targetAttr="); 1272 buffer.append(targetAttribute); 1273 buffer.append(", matchAll="); 1274 buffer.append(matchAll); 1275 buffer.append(')'); 1276 break; 1277 1278 case JOIN_TYPE_REVERSE_DN: 1279 buffer.append("ReverseDNJoinRule(targetAttr="); 1280 buffer.append(targetAttribute); 1281 buffer.append(')'); 1282 break; 1283 } 1284 } 1285}