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