001/* 002 * Copyright 2009-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-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) 2009-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.io.Serializable; 041import java.util.List; 042 043import com.unboundid.asn1.ASN1Boolean; 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.asn1.ASN1Sequence; 047import com.unboundid.asn1.ASN1Set; 048import com.unboundid.ldap.sdk.LDAPException; 049import com.unboundid.ldap.sdk.ResultCode; 050import com.unboundid.util.Debug; 051import com.unboundid.util.NotMutable; 052import com.unboundid.util.NotNull; 053import com.unboundid.util.Nullable; 054import com.unboundid.util.StaticUtils; 055import com.unboundid.util.ThreadSafety; 056import com.unboundid.util.ThreadSafetyLevel; 057import com.unboundid.util.Validator; 058 059import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 060 061 062 063/** 064 * This class provides an implementation of a join rule as used by the LDAP join 065 * request control. See the class-level documentation for the 066 * {@link JoinRequestControl} class for additional information and an example 067 * demonstrating its use. 068 * <BR> 069 * <BLOCKQUOTE> 070 * <B>NOTE:</B> This class, and other classes within the 071 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 072 * supported for use against Ping Identity, UnboundID, and 073 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 074 * for proprietary functionality or for external specifications that are not 075 * considered stable or mature enough to be guaranteed to work in an 076 * interoperable way with other types of LDAP servers. 077 * </BLOCKQUOTE> 078 * <BR> 079 * Join rules are encoded as follows: 080 * <PRE> 081 * JoinRule ::= CHOICE { 082 * and [0] SET (1 .. MAX) of JoinRule, 083 * or [1] SET (1 .. MAX) of JoinRule, 084 * dnJoin [2] AttributeDescription, 085 * equalityJoin [3] JoinRuleAssertion, 086 * containsJoin [4] JoinRuleAssertion, 087 * reverseDNJoin [5] AttributeDescription, 088 * ... } 089 * 090 * JoinRuleAssertion ::= SEQUENCE { 091 * sourceAttribute AttributeDescription, 092 * targetAttribute AttributeDescription, 093 * matchAll BOOLEAN DEFAULT FALSE } 094 * </PRE> 095 */ 096@NotMutable() 097@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 098public final class JoinRule 099 implements Serializable 100{ 101 /** 102 * The join rule type that will be used for AND join rules. 103 */ 104 public static final byte JOIN_TYPE_AND = (byte) 0xA0; 105 106 107 108 /** 109 * The join rule type that will be used for OR join rules. 110 */ 111 public static final byte JOIN_TYPE_OR = (byte) 0xA1; 112 113 114 115 /** 116 * The join rule type that will be used for DN join rules. 117 */ 118 public static final byte JOIN_TYPE_DN = (byte) 0x82; 119 120 121 122 /** 123 * The join rule type that will be used for equality join rules. 124 */ 125 public static final byte JOIN_TYPE_EQUALITY = (byte) 0xA3; 126 127 128 129 /** 130 * The join rule type that will be used for contains join rules. 131 */ 132 public static final byte JOIN_TYPE_CONTAINS = (byte) 0xA4; 133 134 135 136 /** 137 * The join rule type that will be used for reverse DN join rules. 138 */ 139 public static final byte JOIN_TYPE_REVERSE_DN = (byte) 0x85; 140 141 142 143 /** 144 * An empty array of join rules that will be used as the set of components 145 * for DN and equality join rules. 146 */ 147 @NotNull private static final JoinRule[] NO_RULES = new JoinRule[0]; 148 149 150 151 /** 152 * The serial version UID for this serializable class. 153 */ 154 private static final long serialVersionUID = 9041070342511946580L; 155 156 157 158 // Indicates whether all values of a multivalued source attribute must be 159 // present in the target entry for it to be considered a match. 160 private final boolean matchAll; 161 162 // The BER type for this join rule. 163 private final byte type; 164 165 // The set of subordinate components for this join rule. 166 @NotNull private final JoinRule[] components; 167 168 // The name of the source attribute for this join rule. 169 @Nullable private final String sourceAttribute; 170 171 // The name of the target attribute for this join rule. 172 @Nullable private final String targetAttribute; 173 174 175 176 /** 177 * Creates a new join rule with the provided information. 178 * 179 * @param type The BER type for this join rule. 180 * @param components The set of subordinate components for this join 181 * rule. 182 * @param sourceAttribute The name of the source attribute for this join 183 * rule. 184 * @param targetAttribute The name of the target attribute for this join 185 * rule. 186 * @param matchAll Indicates whether all values of a multivalued 187 * source attribute must be present in the target 188 * entry for it to be considered a match. 189 */ 190 private JoinRule(final byte type, @NotNull final JoinRule[] components, 191 @Nullable final String sourceAttribute, 192 @Nullable final String targetAttribute, 193 final boolean matchAll) 194 { 195 this.type = type; 196 this.components = components; 197 this.sourceAttribute = sourceAttribute; 198 this.targetAttribute = targetAttribute; 199 this.matchAll = matchAll; 200 } 201 202 203 204 /** 205 * Creates an AND join rule in which all of the contained join rules must 206 * match an entry for it to be included in the join. 207 * 208 * @param components The set of components to include in this join. It must 209 * not be {@code null} or empty. 210 * 211 * @return The created AND join rule. 212 */ 213 @NotNull() 214 public static JoinRule createANDRule(@NotNull final JoinRule... components) 215 { 216 Validator.ensureNotNull(components); 217 Validator.ensureFalse(components.length == 0); 218 219 return new JoinRule(JOIN_TYPE_AND, components, null, null, false); 220 } 221 222 223 224 /** 225 * Creates an AND join rule in which all of the contained join rules must 226 * match an entry for it to be included in the join. 227 * 228 * @param components The set of components to include in this join. It must 229 * not be {@code null} or empty. 230 * 231 * @return The created AND join rule. 232 */ 233 @NotNull() 234 public static JoinRule createANDRule(@NotNull final List<JoinRule> components) 235 { 236 Validator.ensureNotNull(components); 237 Validator.ensureFalse(components.isEmpty()); 238 239 final JoinRule[] compArray = new JoinRule[components.size()]; 240 return new JoinRule(JOIN_TYPE_AND, components.toArray(compArray), null, 241 null, false); 242 } 243 244 245 246 /** 247 * Creates an OR join rule in which at least one of the contained join rules 248 * must match an entry for it to be included in the join. 249 * 250 * @param components The set of components to include in this join. It must 251 * not be {@code null} or empty. 252 * 253 * @return The created OR join rule. 254 */ 255 @NotNull() 256 public static JoinRule createORRule(@NotNull final JoinRule... components) 257 { 258 Validator.ensureNotNull(components); 259 Validator.ensureFalse(components.length == 0); 260 261 return new JoinRule(JOIN_TYPE_OR, components, null, null, false); 262 } 263 264 265 266 /** 267 * Creates an OR join rule in which at least one of the contained join rules 268 * must match an entry for it to be included in the join. 269 * 270 * @param components The set of components to include in this join. It must 271 * not be {@code null} or empty. 272 * 273 * @return The created OR join rule. 274 */ 275 @NotNull() 276 public static JoinRule createORRule(@NotNull final List<JoinRule> components) 277 { 278 Validator.ensureNotNull(components); 279 Validator.ensureFalse(components.isEmpty()); 280 281 final JoinRule[] compArray = new JoinRule[components.size()]; 282 return new JoinRule(JOIN_TYPE_OR, components.toArray(compArray), null, 283 null, false); 284 } 285 286 287 288 /** 289 * Creates a DN join rule in which the value(s) of the source attribute must 290 * specify the DN(s) of the target entries to include in the join. 291 * 292 * @param sourceAttribute The name or OID of the attribute in the source 293 * entry whose values contain the DNs of the entries 294 * to be included in the join. It must not be 295 * {@code null}, and it must be associated with a 296 * distinguished name or name and optional UID 297 * syntax. 298 * 299 * @return The created DN join rule. 300 */ 301 @NotNull() 302 public static JoinRule createDNJoin(@NotNull final String sourceAttribute) 303 { 304 Validator.ensureNotNull(sourceAttribute); 305 306 return new JoinRule(JOIN_TYPE_DN, NO_RULES, sourceAttribute, null, false); 307 } 308 309 310 311 /** 312 * Creates an equality join rule in which the value(s) of the source attribute 313 * in the source entry must be equal to the value(s) of the target attribute 314 * of a target entry for it to be included in the join. 315 * 316 * @param sourceAttribute The name or OID of the attribute in the source 317 * entry whose value(s) should be matched in target 318 * entries to be included in the join. It must not 319 * be {@code null}. 320 * @param targetAttribute The name or OID of the attribute whose value(s) 321 * must match the source value(s) in entries included 322 * in the join. It must not be {@code null}. 323 * @param matchAll Indicates whether all values of a multivalued 324 * source attribute must be present in the target 325 * entry for it to be considered a match. 326 * 327 * @return The created equality join rule. 328 */ 329 @NotNull() 330 public static JoinRule createEqualityJoin( 331 @NotNull final String sourceAttribute, 332 @NotNull final String targetAttribute, 333 final boolean matchAll) 334 { 335 Validator.ensureNotNull(sourceAttribute, targetAttribute); 336 337 return new JoinRule(JOIN_TYPE_EQUALITY, NO_RULES, sourceAttribute, 338 targetAttribute, matchAll); 339 } 340 341 342 343 /** 344 * Creates an equality join rule in which the value(s) of the source attribute 345 * in the source entry must be equal to or a substring of the value(s) of the 346 * target attribute of a target entry for it to be included in the join. 347 * 348 * @param sourceAttribute The name or OID of the attribute in the source 349 * entry whose value(s) should be matched in target 350 * entries to be included in the join. It must not 351 * be {@code null}. 352 * @param targetAttribute The name or OID of the attribute whose value(s) 353 * must equal or contain the source value(s) in 354 * entries included in the join. It must not be 355 * {@code null}. 356 * @param matchAll Indicates whether all values of a multivalued 357 * source attribute must be present in the target 358 * entry for it to be considered a match. 359 * 360 * @return The created equality join rule. 361 */ 362 @NotNull() 363 public static JoinRule createContainsJoin( 364 @NotNull final String sourceAttribute, 365 @NotNull final String targetAttribute, 366 final boolean matchAll) 367 { 368 Validator.ensureNotNull(sourceAttribute, targetAttribute); 369 370 return new JoinRule(JOIN_TYPE_CONTAINS, NO_RULES, sourceAttribute, 371 targetAttribute, matchAll); 372 } 373 374 375 376 /** 377 * Creates a reverse DN join rule in which the target entries to include in 378 * the join must include a specified attribute that contains the DN of the 379 * source entry. 380 * 381 * @param targetAttribute The name or OID of the attribute in the target 382 * entries which must contain the DN of the source 383 * entry. It must not be {@code null}, and it must 384 * be associated with a distinguished nme or name and 385 * optional UID syntax. 386 * 387 * @return The created reverse DN join rule. 388 */ 389 @NotNull() 390 public static JoinRule createReverseDNJoin( 391 @NotNull final String targetAttribute) 392 { 393 Validator.ensureNotNull(targetAttribute); 394 395 return new JoinRule(JOIN_TYPE_REVERSE_DN, NO_RULES, null, targetAttribute, 396 false); 397 } 398 399 400 401 /** 402 * Retrieves the join rule type for this join rule. 403 * 404 * @return The join rule type for this join rule. 405 */ 406 public byte getType() 407 { 408 return type; 409 } 410 411 412 413 /** 414 * Retrieves the set of subordinate components for this AND or OR join rule. 415 * 416 * @return The set of subordinate components for this AND or OR join rule, or 417 * an empty list if this is not an AND or OR join rule. 418 */ 419 @NotNull() 420 public JoinRule[] getComponents() 421 { 422 return components; 423 } 424 425 426 427 /** 428 * Retrieves the name of the source attribute for this DN, equality, or 429 * contains join rule. 430 * 431 * @return The name of the source attribute for this DN, equality, or 432 * contains join rule, or {@code null} if this is some other type of 433 * join rule. 434 */ 435 @Nullable() 436 public String getSourceAttribute() 437 { 438 return sourceAttribute; 439 } 440 441 442 443 /** 444 * Retrieves the name of the target attribute for this reverse DN, equality, 445 * or contains join rule. 446 * 447 * @return The name of the target attribute for this reverse DN, equality, or 448 * contains join rule, or {@code null} if this is some other type of 449 * join rule. 450 */ 451 @Nullable() 452 public String getTargetAttribute() 453 { 454 return targetAttribute; 455 } 456 457 458 459 /** 460 * Indicates whether all values of a multivalued source attribute must be 461 * present in a target entry for it to be considered a match. The return 462 * value will only be meaningful for equality join rules. 463 * 464 * @return {@code true} if all values of the source attribute must be 465 * included in the target attribute of an entry for it to be 466 * considered for inclusion in the join, or {@code false} if it is 467 * only necessary for at least one of the values to be included in a 468 * target entry for it to be considered for inclusion in the join. 469 */ 470 public boolean matchAll() 471 { 472 return matchAll; 473 } 474 475 476 477 /** 478 * Encodes this join rule as appropriate for inclusion in an LDAP join 479 * request control. 480 * 481 * @return The encoded representation of this join rule. 482 */ 483 @NotNull() 484 ASN1Element encode() 485 { 486 switch (type) 487 { 488 case JOIN_TYPE_AND: 489 case JOIN_TYPE_OR: 490 final ASN1Element[] compElements = new ASN1Element[components.length]; 491 for (int i=0; i < components.length; i++) 492 { 493 compElements[i] = components[i].encode(); 494 } 495 return new ASN1Set(type, compElements); 496 497 case JOIN_TYPE_DN: 498 return new ASN1OctetString(type, sourceAttribute); 499 500 case JOIN_TYPE_EQUALITY: 501 case JOIN_TYPE_CONTAINS: 502 if (matchAll) 503 { 504 return new ASN1Sequence(type, 505 new ASN1OctetString(sourceAttribute), 506 new ASN1OctetString(targetAttribute), 507 new ASN1Boolean(matchAll)); 508 } 509 else 510 { 511 return new ASN1Sequence(type, 512 new ASN1OctetString(sourceAttribute), 513 new ASN1OctetString(targetAttribute)); 514 } 515 case JOIN_TYPE_REVERSE_DN: 516 return new ASN1OctetString(type, targetAttribute); 517 518 default: 519 // This should never happen. 520 return null; 521 } 522 } 523 524 525 526 /** 527 * Decodes the provided ASN.1 element as a join rule. 528 * 529 * @param element The element to be decoded. 530 * 531 * @return The decoded join rule. 532 * 533 * @throws LDAPException If a problem occurs while attempting to decode the 534 * provided element as a join rule. 535 */ 536 @NotNull() 537 static JoinRule decode(@NotNull final ASN1Element element) 538 throws LDAPException 539 { 540 final byte elementType = element.getType(); 541 switch (elementType) 542 { 543 case JOIN_TYPE_AND: 544 case JOIN_TYPE_OR: 545 try 546 { 547 final ASN1Element[] elements = 548 ASN1Set.decodeAsSet(element).elements(); 549 final JoinRule[] rules = new JoinRule[elements.length]; 550 for (int i=0; i < rules.length; i++) 551 { 552 rules[i] = decode(elements[i]); 553 } 554 555 return new JoinRule(elementType, rules, null, null, false); 556 } 557 catch (final Exception e) 558 { 559 Debug.debugException(e); 560 561 throw new LDAPException(ResultCode.DECODING_ERROR, 562 ERR_JOIN_RULE_CANNOT_DECODE.get( 563 StaticUtils.getExceptionMessage(e)), 564 e); 565 } 566 567 568 case JOIN_TYPE_DN: 569 return new JoinRule(elementType, NO_RULES, 570 ASN1OctetString.decodeAsOctetString(element).stringValue(), null, 571 false); 572 573 574 case JOIN_TYPE_EQUALITY: 575 case JOIN_TYPE_CONTAINS: 576 try 577 { 578 final ASN1Element[] elements = 579 ASN1Sequence.decodeAsSequence(element).elements(); 580 581 final String sourceAttribute = 582 elements[0].decodeAsOctetString().stringValue(); 583 final String targetAttribute = 584 elements[1].decodeAsOctetString().stringValue(); 585 586 boolean matchAll = false; 587 if (elements.length == 3) 588 { 589 matchAll = elements[2].decodeAsBoolean().booleanValue(); 590 } 591 592 return new JoinRule(elementType, NO_RULES, sourceAttribute, 593 targetAttribute, matchAll); 594 } 595 catch (final Exception e) 596 { 597 Debug.debugException(e); 598 599 throw new LDAPException(ResultCode.DECODING_ERROR, 600 ERR_JOIN_RULE_CANNOT_DECODE.get( 601 StaticUtils.getExceptionMessage(e)), 602 e); 603 } 604 605 606 case JOIN_TYPE_REVERSE_DN: 607 return new JoinRule(elementType, NO_RULES, null, 608 ASN1OctetString.decodeAsOctetString(element).stringValue(), false); 609 610 611 default: 612 throw new LDAPException(ResultCode.DECODING_ERROR, 613 ERR_JOIN_RULE_DECODE_INVALID_TYPE.get( 614 StaticUtils.toHex(elementType))); 615 } 616 } 617 618 619 620 /** 621 * Retrieves a string representation of this join rule. 622 * 623 * @return A string representation of this join rule. 624 */ 625 @Override() 626 @NotNull() 627 public String toString() 628 { 629 final StringBuilder buffer = new StringBuilder(); 630 toString(buffer); 631 return buffer.toString(); 632 } 633 634 635 636 /** 637 * Appends a string representation of this join rule to the provided buffer. 638 * 639 * @param buffer The buffer to which the information should be appended. 640 */ 641 public void toString(@NotNull final StringBuilder buffer) 642 { 643 switch (type) 644 { 645 case JOIN_TYPE_AND: 646 buffer.append("ANDJoinRule(components={"); 647 for (int i=0; i < components.length; i++) 648 { 649 if (i > 0) 650 { 651 buffer.append(", "); 652 } 653 components[i].toString(buffer); 654 } 655 buffer.append("})"); 656 break; 657 658 case JOIN_TYPE_OR: 659 buffer.append("ORJoinRule(components={"); 660 for (int i=0; i < components.length; i++) 661 { 662 if (i > 0) 663 { 664 buffer.append(", "); 665 } 666 components[i].toString(buffer); 667 } 668 buffer.append("})"); 669 break; 670 671 case JOIN_TYPE_DN: 672 buffer.append("DNJoinRule(sourceAttr="); 673 buffer.append(sourceAttribute); 674 buffer.append(')'); 675 break; 676 677 case JOIN_TYPE_EQUALITY: 678 buffer.append("EqualityJoinRule(sourceAttr="); 679 buffer.append(sourceAttribute); 680 buffer.append(", targetAttr="); 681 buffer.append(targetAttribute); 682 buffer.append(", matchAll="); 683 buffer.append(matchAll); 684 buffer.append(')'); 685 break; 686 687 case JOIN_TYPE_CONTAINS: 688 buffer.append("ContainsJoinRule(sourceAttr="); 689 buffer.append(sourceAttribute); 690 buffer.append(", targetAttr="); 691 buffer.append(targetAttribute); 692 buffer.append(", matchAll="); 693 buffer.append(matchAll); 694 buffer.append(')'); 695 break; 696 697 case JOIN_TYPE_REVERSE_DN: 698 buffer.append("ReverseDNJoinRule(targetAttr="); 699 buffer.append(targetAttribute); 700 buffer.append(')'); 701 break; 702 } 703 } 704}