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