001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.types; 028 029import java.util.*; 030import java.util.regex.Pattern; 031 032import org.forgerock.i18n.LocalizableMessage; 033import org.forgerock.opendj.ldap.ResultCode; 034import org.opends.server.core.DirectoryServer; 035import org.opends.server.util.StaticUtils; 036 037import static org.opends.messages.SchemaMessages.*; 038 039/** 040 * An RFC 3672 subtree specification. 041 * <p> 042 * This implementation extends RFC 3672 by supporting search filters 043 * for specification filters. More specifically, the 044 * {@code Refinement} product has been extended as follows: 045 * 046 * <pre> 047 * Refinement = item / and / or / not / Filter 048 * 049 * Filter = dquote *SafeUTF8Character dquote 050 * </pre> 051 * 052 * @see <a href="http://tools.ietf.org/html/rfc3672">RFC 3672 - 053 * Subentries in the Lightweight Directory Access Protocol (LDAP) 054 * </a> 055 */ 056@org.opends.server.types.PublicAPI( 057 stability = org.opends.server.types.StabilityLevel.VOLATILE, 058 mayInstantiate = false, 059 mayExtend = true, 060 mayInvoke = false) 061public final class SubtreeSpecification 062{ 063 064 /** 065 * RFC 3672 subtree specification AND refinement. This type of 066 * refinement filters entries based on all of the underlying 067 * refinements being <code>true</code>. 068 */ 069 public static final class AndRefinement extends Refinement 070 { 071 /** The set of refinements which must all be true. */ 072 private final Collection<Refinement> refinementSet; 073 074 075 076 /** 077 * Create a new AND refinement. 078 * 079 * @param refinementSet 080 * The set of refinements which must all be 081 * <code>true</code>. 082 */ 083 public AndRefinement(final Collection<Refinement> refinementSet) 084 { 085 this.refinementSet = refinementSet; 086 } 087 088 089 090 /** {@inheritDoc} */ 091 @Override 092 public boolean equals(final Object obj) 093 { 094 if (this == obj) 095 { 096 return true; 097 } 098 099 if (obj instanceof AndRefinement) 100 { 101 final AndRefinement other = (AndRefinement) obj; 102 103 return refinementSet.equals(other.refinementSet); 104 } 105 106 return false; 107 } 108 109 110 111 /** {@inheritDoc} */ 112 @Override 113 public int hashCode() 114 { 115 return refinementSet.hashCode(); 116 } 117 118 119 120 /** {@inheritDoc} */ 121 @Override 122 public boolean matches(final Entry entry) 123 { 124 for (final Refinement refinement : refinementSet) 125 { 126 if (!refinement.matches(entry)) 127 { 128 return false; 129 } 130 } 131 132 // All sub-refinements matched. 133 return true; 134 } 135 136 137 138 /** {@inheritDoc} */ 139 @Override 140 public StringBuilder toString(final StringBuilder builder) 141 { 142 switch (refinementSet.size()) 143 { 144 case 0: 145 // Do nothing. 146 break; 147 case 1: 148 refinementSet.iterator().next().toString(builder); 149 break; 150 default: 151 builder.append("and:{"); 152 final Iterator<Refinement> iterator = refinementSet 153 .iterator(); 154 iterator.next().toString(builder); 155 while (iterator.hasNext()) 156 { 157 builder.append(", "); 158 iterator.next().toString(builder); 159 } 160 builder.append("}"); 161 break; 162 } 163 164 return builder; 165 } 166 } 167 168 169 170 /** 171 * A refinement which uses a search filter. 172 */ 173 public static final class FilterRefinement extends Refinement 174 { 175 /** The search filter. */ 176 private final SearchFilter filter; 177 178 179 180 /** 181 * Create a new filter refinement. 182 * 183 * @param filter 184 * The search filter. 185 */ 186 public FilterRefinement(final SearchFilter filter) 187 { 188 this.filter = filter; 189 } 190 191 192 193 /** {@inheritDoc} */ 194 @Override 195 public boolean equals(final Object obj) 196 { 197 if (this == obj) 198 { 199 return true; 200 } 201 202 if (obj instanceof FilterRefinement) 203 { 204 final FilterRefinement other = (FilterRefinement) obj; 205 return filter.equals(other.filter); 206 } 207 208 return false; 209 } 210 211 212 213 /** {@inheritDoc} */ 214 @Override 215 public int hashCode() 216 { 217 return filter.hashCode(); 218 } 219 220 221 222 /** {@inheritDoc} */ 223 @Override 224 public boolean matches(final Entry entry) 225 { 226 try 227 { 228 return filter.matchesEntry(entry); 229 } 230 catch (final DirectoryException e) 231 { 232 // TODO: need to decide what to do with the exception here. 233 // It's probably safe to ignore, but we could log it perhaps. 234 return false; 235 } 236 } 237 238 239 240 /** {@inheritDoc} */ 241 @Override 242 public StringBuilder toString(final StringBuilder builder) 243 { 244 StaticUtils.toRFC3641StringValue(builder, filter.toString()); 245 return builder; 246 } 247 } 248 249 250 251 /** 252 * RFC 3672 subtree specification Item refinement. This type of 253 * refinement filters entries based on the presence of a specified 254 * object class. 255 */ 256 public static final class ItemRefinement extends Refinement 257 { 258 /** The item's object class. */ 259 private final String objectClass; 260 261 /** The item's normalized object class. */ 262 private final String normalizedObjectClass; 263 264 265 266 /** 267 * Create a new item refinement. 268 * 269 * @param objectClass 270 * The item's object class. 271 */ 272 public ItemRefinement(final String objectClass) 273 { 274 this.objectClass = objectClass; 275 this.normalizedObjectClass = StaticUtils 276 .toLowerCase(objectClass.trim()); 277 } 278 279 280 281 /** {@inheritDoc} */ 282 @Override 283 public boolean equals(final Object obj) 284 { 285 if (this == obj) 286 { 287 return true; 288 } 289 290 if (obj instanceof ItemRefinement) 291 { 292 final ItemRefinement other = (ItemRefinement) obj; 293 294 return normalizedObjectClass 295 .equals(other.normalizedObjectClass); 296 } 297 298 return false; 299 } 300 301 302 303 /** {@inheritDoc} */ 304 @Override 305 public int hashCode() 306 { 307 return normalizedObjectClass.hashCode(); 308 } 309 310 311 312 /** {@inheritDoc} */ 313 @Override 314 public boolean matches(final Entry entry) 315 { 316 final ObjectClass oc = DirectoryServer.getObjectClass(normalizedObjectClass); 317 return oc != null && entry.hasObjectClass(oc); 318 } 319 320 321 322 /** {@inheritDoc} */ 323 @Override 324 public StringBuilder toString(final StringBuilder builder) 325 { 326 builder.append("item:"); 327 builder.append(objectClass); 328 return builder; 329 } 330 } 331 332 333 334 /** 335 * RFC 3672 subtree specification NOT refinement. This type of 336 * refinement filters entries based on the underlying refinement 337 * being <code>false</code> 338 * . 339 */ 340 public static final class NotRefinement extends Refinement 341 { 342 /** The inverted refinement. */ 343 private final Refinement refinement; 344 345 346 347 /** 348 * Create a new NOT refinement. 349 * 350 * @param refinement 351 * The refinement which must be <code>false</code>. 352 */ 353 public NotRefinement(final Refinement refinement) 354 { 355 this.refinement = refinement; 356 } 357 358 359 360 /** {@inheritDoc} */ 361 @Override 362 public boolean equals(final Object obj) 363 { 364 if (this == obj) 365 { 366 return true; 367 } 368 369 if (obj instanceof NotRefinement) 370 { 371 final NotRefinement other = (NotRefinement) obj; 372 373 return refinement.equals(other.refinement); 374 } 375 376 return false; 377 } 378 379 380 381 /** {@inheritDoc} */ 382 @Override 383 public int hashCode() 384 { 385 return refinement.hashCode(); 386 } 387 388 389 390 /** {@inheritDoc} */ 391 @Override 392 public boolean matches(final Entry entry) 393 { 394 return !refinement.matches(entry); 395 } 396 397 398 399 /** {@inheritDoc} */ 400 @Override 401 public StringBuilder toString(final StringBuilder builder) 402 { 403 builder.append("not:"); 404 return refinement.toString(builder); 405 } 406 } 407 408 409 410 /** 411 * RFC 3672 subtree specification OR refinement. This type of 412 * refinement filters entries based on at least one of the 413 * underlying refinements being <code>true</code>. 414 */ 415 public static final class OrRefinement extends Refinement 416 { 417 /** The set of refinements of which at least one must be true. */ 418 private final Collection<Refinement> refinementSet; 419 420 421 422 /** 423 * Create a new OR refinement. 424 * 425 * @param refinementSet 426 * The set of refinements of which at least one must be 427 * <code>true</code>. 428 */ 429 public OrRefinement(final Collection<Refinement> refinementSet) 430 { 431 this.refinementSet = refinementSet; 432 } 433 434 435 436 /** {@inheritDoc} */ 437 @Override 438 public boolean equals(final Object obj) 439 { 440 if (this == obj) 441 { 442 return true; 443 } 444 445 if (obj instanceof AndRefinement) 446 { 447 final AndRefinement other = (AndRefinement) obj; 448 449 return refinementSet.equals(other.refinementSet); 450 } 451 452 return false; 453 } 454 455 456 457 /** {@inheritDoc} */ 458 @Override 459 public int hashCode() 460 { 461 return refinementSet.hashCode(); 462 } 463 464 465 466 /** {@inheritDoc} */ 467 @Override 468 public boolean matches(final Entry entry) 469 { 470 for (final Refinement refinement : refinementSet) 471 { 472 if (refinement.matches(entry)) 473 { 474 return true; 475 } 476 } 477 478 // No sub-refinements matched. 479 return false; 480 } 481 482 483 484 /** {@inheritDoc} */ 485 @Override 486 public StringBuilder toString(final StringBuilder builder) 487 { 488 switch (refinementSet.size()) 489 { 490 case 0: 491 // Do nothing. 492 break; 493 case 1: 494 refinementSet.iterator().next().toString(builder); 495 break; 496 default: 497 builder.append("or:{"); 498 final Iterator<Refinement> iterator = refinementSet 499 .iterator(); 500 iterator.next().toString(builder); 501 while (iterator.hasNext()) 502 { 503 builder.append(", "); 504 iterator.next().toString(builder); 505 } 506 builder.append("}"); 507 break; 508 } 509 510 return builder; 511 } 512 } 513 514 515 516 /** 517 * Abstract interface for RFC3672 specification filter refinements. 518 */ 519 public static abstract class Refinement 520 { 521 /** 522 * Create a new RFC3672 specification filter refinement. 523 */ 524 protected Refinement() 525 { 526 // No implementation required. 527 } 528 529 530 531 /** {@inheritDoc} */ 532 @Override 533 public abstract boolean equals(Object obj); 534 535 536 537 /** {@inheritDoc} */ 538 @Override 539 public abstract int hashCode(); 540 541 542 543 /** 544 * Check if the refinement matches the given entry. 545 * 546 * @param entry 547 * The filterable entry. 548 * @return Returns <code>true</code> if the entry matches the 549 * refinement, or <code>false</code> otherwise. 550 */ 551 public abstract boolean matches(Entry entry); 552 553 554 555 /** {@inheritDoc} */ 556 @Override 557 public final String toString() 558 { 559 final StringBuilder builder = new StringBuilder(); 560 561 return toString(builder).toString(); 562 } 563 564 565 566 /** 567 * Append the string representation of the refinement to the 568 * provided strin builder. 569 * 570 * @param builder 571 * The string builder. 572 * @return The string builder. 573 */ 574 public abstract StringBuilder toString(StringBuilder builder); 575 } 576 577 578 579 /** 580 * Internal utility class which can be used by sub-classes to help 581 * parse string representations. 582 */ 583 protected static final class Parser 584 { 585 /** Text scanner used to parse the string value. */ 586 private final Scanner scanner; 587 588 /** Pattern used to detect left braces. */ 589 private static Pattern LBRACE = Pattern.compile("\\{.*"); 590 /** Pattern used to parse left braces. */ 591 private static Pattern LBRACE_TOKEN = Pattern.compile("\\{"); 592 /** Pattern used to detect right braces. */ 593 private static Pattern RBRACE = Pattern.compile("\\}.*"); 594 /** Pattern used to parse right braces. */ 595 private static Pattern RBRACE_TOKEN = Pattern.compile("\\}"); 596 /** Pattern used to detect comma separators. */ 597 private static Pattern SEP = Pattern.compile(",.*"); 598 /** Pattern used to parse comma separators. */ 599 private static Pattern SEP_TOKEN = Pattern.compile(","); 600 /** Pattern used to detect colon separators. */ 601 private static Pattern COLON = Pattern.compile(":.*"); 602 /** Pattern used to parse colon separators. */ 603 private static Pattern COLON_TOKEN = Pattern.compile(":"); 604 /** Pattern used to detect integer values. */ 605 private static Pattern INT = Pattern.compile("\\d.*"); 606 /** Pattern used to parse integer values. */ 607 private static Pattern INT_TOKEN = Pattern.compile("\\d+"); 608 /** Pattern used to detect name values. */ 609 private static Pattern NAME = Pattern.compile("[\\w_;-].*"); 610 /** Pattern used to parse name values. */ 611 private static Pattern NAME_TOKEN = Pattern.compile("[\\w_;-]+"); 612 /** Pattern used to detect RFC3641 string values. */ 613 private static Pattern STRING_VALUE = Pattern.compile("\".*"); 614 /** Pattern used to parse RFC3641 string values. */ 615 private static Pattern STRING_VALUE_TOKEN = Pattern 616 .compile("\"([^\"]|(\"\"))*\""); 617 618 619 620 /** 621 * Create a new parser for a subtree specification string value. 622 * 623 * @param value 624 * The subtree specification string value. 625 */ 626 public Parser(final String value) 627 { 628 this.scanner = new Scanner(value); 629 } 630 631 632 633 /** 634 * Determine if there are remaining tokens. 635 * 636 * @return <code>true</code> if and only if there are remaining 637 * tokens. 638 */ 639 public boolean hasNext() 640 { 641 return scanner.hasNext(); 642 } 643 644 645 646 /** 647 * Determine if the next token is a right-brace character. 648 * 649 * @return <code>true</code> if and only if the next token is a 650 * valid right brace character. 651 */ 652 public boolean hasNextRightBrace() 653 { 654 return scanner.hasNext(RBRACE); 655 } 656 657 658 659 /** 660 * Scans the next token of the input as a non-negative 661 * <code>int</code> value. 662 * 663 * @return The name value scanned from the input. 664 * @throws InputMismatchException 665 * If the next token is not a valid non-negative integer 666 * string. 667 * @throws NoSuchElementException 668 * If input is exhausted. 669 */ 670 public int nextInt() throws InputMismatchException, 671 NoSuchElementException 672 { 673 final String s = nextValue(INT, INT_TOKEN); 674 return Integer.parseInt(s); 675 } 676 677 678 679 /** 680 * Scans the next token of the input as a key value. 681 * 682 * @return The lower-case key value scanned from the input. 683 * @throws InputMismatchException 684 * If the next token is not a valid key string. 685 * @throws NoSuchElementException 686 * If input is exhausted. 687 */ 688 public String nextKey() throws InputMismatchException, 689 NoSuchElementException 690 { 691 return StaticUtils.toLowerCase(scanner.next()); 692 } 693 694 695 696 /** 697 * Scans the next token of the input as a name value. 698 * <p> 699 * A name is any string containing only alpha-numeric characters 700 * or hyphens, semi-colons, or underscores. 701 * 702 * @return The name value scanned from the input. 703 * @throws InputMismatchException 704 * If the next token is not a valid name string. 705 * @throws NoSuchElementException 706 * If input is exhausted. 707 */ 708 public String nextName() throws InputMismatchException, 709 NoSuchElementException 710 { 711 return nextValue(NAME, NAME_TOKEN); 712 } 713 714 715 716 /** 717 * Scans the next tokens of the input as a set of specific 718 * exclusions encoded according to the SpecificExclusion 719 * production in RFC 3672. 720 * 721 * @param chopBefore 722 * The set of chop before local names. 723 * @param chopAfter 724 * The set of chop after local names. 725 * @throws InputMismatchException 726 * If the common component did not have a valid syntax. 727 * @throws NoSuchElementException 728 * If input is exhausted. 729 * @throws DirectoryException 730 * If an error occurred when attempting to parse a 731 * DN value. 732 */ 733 public void nextSpecificExclusions(final Set<DN> chopBefore, 734 final Set<DN> chopAfter) throws InputMismatchException, 735 NoSuchElementException, DirectoryException 736 { 737 738 // Skip leading open-brace. 739 skipLeftBrace(); 740 741 // Parse each chop DN in the sequence. 742 boolean isFirstValue = true; 743 while (true) 744 { 745 // Make sure that there is a closing brace. 746 if (hasNextRightBrace()) 747 { 748 skipRightBrace(); 749 break; 750 } 751 752 // Make sure that there is a comma separator if this is not 753 // the first element. 754 if (!isFirstValue) 755 { 756 skipSeparator(); 757 } 758 else 759 { 760 isFirstValue = false; 761 } 762 763 // Parse each chop specification which is of the form 764 // <type>:<value>. 765 final String type = StaticUtils.toLowerCase(nextName()); 766 skipColon(); 767 if (type.equals("chopbefore")) 768 { 769 chopBefore.add(DN.valueOf(nextStringValue())); 770 } 771 else if (type.equals("chopafter")) 772 { 773 chopAfter.add(DN.valueOf(nextStringValue())); 774 } 775 else 776 { 777 throw new java.util.InputMismatchException(); 778 } 779 } 780 } 781 782 783 784 /** 785 * Scans the next token of the input as a string quoted according 786 * to the StringValue production in RFC 3641. 787 * <p> 788 * The return string has its outer double quotes removed and any 789 * escape inner double quotes unescaped. 790 * 791 * @return The string value scanned from the input. 792 * @throws InputMismatchException 793 * If the next token is not a valid string. 794 * @throws NoSuchElementException 795 * If input is exhausted. 796 */ 797 public String nextStringValue() throws InputMismatchException, 798 NoSuchElementException 799 { 800 final String s = nextValue(STRING_VALUE, STRING_VALUE_TOKEN); 801 return s.substring(1, s.length() - 1).replace("\"\"", "\""); 802 } 803 804 805 806 /** 807 * Skip a colon separator. 808 * 809 * @throws InputMismatchException 810 * If the next token is not a colon separator character. 811 * @throws NoSuchElementException 812 * If input is exhausted. 813 */ 814 public void skipColon() throws InputMismatchException, 815 NoSuchElementException 816 { 817 nextValue(COLON, COLON_TOKEN); 818 } 819 820 821 822 /** 823 * Skip a left-brace character. 824 * 825 * @throws InputMismatchException 826 * If the next token is not a left-brace character. 827 * @throws NoSuchElementException 828 * If input is exhausted. 829 */ 830 public void skipLeftBrace() throws InputMismatchException, 831 NoSuchElementException 832 { 833 nextValue(LBRACE, LBRACE_TOKEN); 834 } 835 836 837 838 /** 839 * Skip a right-brace character. 840 * 841 * @throws InputMismatchException 842 * If the next token is not a right-brace character. 843 * @throws NoSuchElementException 844 * If input is exhausted. 845 */ 846 public void skipRightBrace() throws InputMismatchException, 847 NoSuchElementException 848 { 849 nextValue(RBRACE, RBRACE_TOKEN); 850 } 851 852 853 854 /** 855 * Skip a comma separator. 856 * 857 * @throws InputMismatchException 858 * If the next token is not a comma separator character. 859 * @throws NoSuchElementException 860 * If input is exhausted. 861 */ 862 public void skipSeparator() throws InputMismatchException, 863 NoSuchElementException 864 { 865 nextValue(SEP, SEP_TOKEN); 866 } 867 868 869 870 /** 871 * Parse the next token from the string using the specified 872 * patterns. 873 * 874 * @param head 875 * The pattern used to determine if the next token is a 876 * possible match. 877 * @param content 878 * The pattern used to parse the token content. 879 * @return The next token matching the <code>content</code> 880 * pattern. 881 * @throws InputMismatchException 882 * If the next token does not match the 883 * <code>content</code> pattern. 884 * @throws NoSuchElementException 885 * If input is exhausted. 886 */ 887 private String nextValue(final Pattern head, 888 final Pattern content) 889 throws InputMismatchException, NoSuchElementException 890 { 891 if (!scanner.hasNext()) 892 { 893 throw new java.util.NoSuchElementException(); 894 } 895 896 if (!scanner.hasNext(head)) 897 { 898 throw new java.util.InputMismatchException(); 899 } 900 901 final String s = scanner.findInLine(content); 902 if (s == null) 903 { 904 throw new java.util.InputMismatchException(); 905 } 906 907 return s; 908 } 909 } 910 911 912 913 /** 914 * Parses the string argument as an RFC3672 subtree specification. 915 * 916 * @param rootDN 917 * The DN of the subtree specification's base entry. 918 * @param s 919 * The string to be parsed. 920 * @return The RFC3672 subtree specification represented by the 921 * string argument. 922 * @throws DirectoryException 923 * If the string does not contain a parsable relative 924 * subtree specification. 925 */ 926 public static SubtreeSpecification valueOf(final DN rootDN, 927 final String s) throws DirectoryException 928 { 929 930 // Default values. 931 DN relativeBaseDN = null; 932 933 int minimum = -1; 934 int maximum = -1; 935 936 final HashSet<DN> chopBefore = new HashSet<>(); 937 final HashSet<DN> chopAfter = new HashSet<>(); 938 939 Refinement refinement = null; 940 941 // Value must have an opening left brace. 942 final Parser parser = new Parser(s); 943 boolean isValid = true; 944 945 try 946 { 947 parser.skipLeftBrace(); 948 949 // Parse each element of the value sequence. 950 boolean isFirst = true; 951 952 while (true) 953 { 954 if (parser.hasNextRightBrace()) 955 { 956 // Make sure that there is a closing brace and no trailing text. 957 parser.skipRightBrace(); 958 959 if (parser.hasNext()) 960 { 961 throw new java.util.InputMismatchException(); 962 } 963 break; 964 } 965 966 // Make sure that there is a comma separator if this is not 967 // the first element. 968 if (!isFirst) 969 { 970 parser.skipSeparator(); 971 } 972 else 973 { 974 isFirst = false; 975 } 976 977 final String key = parser.nextKey(); 978 if (key.equals("base")) 979 { 980 if (relativeBaseDN != null) 981 { 982 // Relative base DN specified more than once. 983 throw new InputMismatchException(); 984 } 985 relativeBaseDN = DN.valueOf(parser.nextStringValue()); 986 } 987 else if (key.equals("minimum")) 988 { 989 if (minimum != -1) 990 { 991 // Minimum specified more than once. 992 throw new InputMismatchException(); 993 } 994 minimum = parser.nextInt(); 995 } 996 else if (key.equals("maximum")) 997 { 998 if (maximum != -1) 999 { 1000 // Maximum specified more than once. 1001 throw new InputMismatchException(); 1002 } 1003 maximum = parser.nextInt(); 1004 } 1005 else if (key.equals("specificationfilter")) 1006 { 1007 if (refinement != null) 1008 { 1009 // Refinements specified more than once. 1010 throw new InputMismatchException(); 1011 } 1012 1013 // First try normal search filter before RFC3672 1014 // refinements. 1015 try 1016 { 1017 final SearchFilter filter = SearchFilter 1018 .createFilterFromString(parser.nextStringValue()); 1019 refinement = new FilterRefinement(filter); 1020 } 1021 catch (final InputMismatchException e) 1022 { 1023 refinement = parseRefinement(parser); 1024 } 1025 } 1026 else if (key.equals("specificexclusions")) 1027 { 1028 if (!chopBefore.isEmpty() || !chopAfter.isEmpty()) 1029 { 1030 // Specific exclusions specified more than once. 1031 throw new InputMismatchException(); 1032 } 1033 1034 parser.nextSpecificExclusions(chopBefore, chopAfter); 1035 } 1036 else 1037 { 1038 throw new InputMismatchException(); 1039 } 1040 } 1041 1042 // Make default minimum value is 0. 1043 if (minimum < 0) 1044 { 1045 minimum = 0; 1046 } 1047 1048 // Check that the maximum, if specified, is gte the minimum. 1049 if (maximum >= 0 && maximum < minimum) 1050 { 1051 isValid = false; 1052 } 1053 } 1054 catch (final NoSuchElementException e) 1055 { 1056 isValid = false; 1057 } 1058 1059 if (isValid) 1060 { 1061 return new SubtreeSpecification(rootDN, relativeBaseDN, 1062 minimum, maximum, chopBefore, chopAfter, refinement); 1063 } 1064 else 1065 { 1066 final LocalizableMessage message = 1067 ERR_ATTR_SYNTAX_RFC3672_SUBTREE_SPECIFICATION_INVALID.get(s); 1068 throw new DirectoryException( 1069 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1070 } 1071 } 1072 1073 1074 1075 /** 1076 * Parse a single refinement. 1077 * 1078 * @param parser 1079 * The active subtree specification parser. 1080 * @return The parsed refinement. 1081 * @throws InputMismatchException 1082 * If the common component did not have a valid syntax. 1083 * @throws NoSuchElementException 1084 * If input is exhausted. 1085 */ 1086 private static Refinement parseRefinement(final Parser parser) 1087 throws InputMismatchException, NoSuchElementException 1088 { 1089 // Get the type of refinement. 1090 final String type = StaticUtils.toLowerCase(parser.nextName()); 1091 1092 // Skip the colon separator. 1093 parser.skipColon(); 1094 1095 if (type.equals("item")) 1096 { 1097 return new ItemRefinement(parser.nextName()); 1098 } 1099 else if (type.equals("not")) 1100 { 1101 final Refinement refinement = parseRefinement(parser); 1102 return new NotRefinement(refinement); 1103 } 1104 else if (type.equals("and")) 1105 { 1106 final ArrayList<Refinement> refinements = 1107 parseRefinementSet(parser); 1108 return new AndRefinement(refinements); 1109 } 1110 else if (type.equals("or")) 1111 { 1112 final ArrayList<Refinement> refinements = 1113 parseRefinementSet(parser); 1114 return new OrRefinement(refinements); 1115 } 1116 else 1117 { 1118 // Unknown refinement type. 1119 throw new InputMismatchException(); 1120 } 1121 } 1122 1123 1124 1125 /** 1126 * Parse a list of refinements. 1127 * 1128 * @param parser 1129 * The active subtree specification parser. 1130 * @return The parsed refinement list. 1131 * @throws InputMismatchException 1132 * If the common component did not have a valid syntax. 1133 * @throws NoSuchElementException 1134 * If input is exhausted. 1135 */ 1136 private static ArrayList<Refinement> parseRefinementSet( 1137 final Parser parser) throws InputMismatchException, 1138 NoSuchElementException 1139 { 1140 final ArrayList<Refinement> refinements = new ArrayList<>(); 1141 1142 // Skip leading open-brace. 1143 parser.skipLeftBrace(); 1144 1145 // Parse each chop DN in the sequence. 1146 boolean isFirstValue = true; 1147 while (true) 1148 { 1149 // Make sure that there is a closing brace. 1150 if (parser.hasNextRightBrace()) 1151 { 1152 parser.skipRightBrace(); 1153 break; 1154 } 1155 1156 // Make sure that there is a comma separator if this is not 1157 // the first element. 1158 if (!isFirstValue) 1159 { 1160 parser.skipSeparator(); 1161 } 1162 else 1163 { 1164 isFirstValue = false; 1165 } 1166 1167 // Parse each sub-refinement. 1168 final Refinement refinement = parseRefinement(parser); 1169 refinements.add(refinement); 1170 } 1171 1172 return refinements; 1173 } 1174 1175 1176 1177 /** The absolute base of the subtree. */ 1178 private final DN baseDN; 1179 1180 /** Optional minimum depth (<=0 means unlimited). */ 1181 private final int minimumDepth; 1182 /** Optional maximum depth (<0 means unlimited). */ 1183 private final int maximumDepth; 1184 1185 /** Optional set of chop before absolute DNs (mapping to their local-names). */ 1186 private final Map<DN, DN> chopBefore; 1187 1188 /** Optional set of chop after absolute DNs (mapping to their local-names). */ 1189 private final Map<DN, DN> chopAfter; 1190 1191 /** The root DN. */ 1192 private final DN rootDN; 1193 1194 /** The optional relative base DN. */ 1195 private final DN relativeBaseDN; 1196 1197 /** The optional specification filter refinements. */ 1198 private final Refinement refinements; 1199 1200 1201 1202 /** 1203 * Create a new RFC3672 subtree specification. 1204 * 1205 * @param rootDN 1206 * The root DN of the subtree. 1207 * @param relativeBaseDN 1208 * The relative base DN (or {@code null} if not 1209 * specified). 1210 * @param minimumDepth 1211 * The minimum depth (less than or equal to 0 means unlimited). 1212 * @param maximumDepth 1213 * The maximum depth (less than 0 means unlimited). 1214 * @param chopBefore 1215 * The set of chop before local names (relative to the 1216 * relative base DN), or {@code null} if there are 1217 * none. 1218 * @param chopAfter 1219 * The set of chop after local names (relative to the 1220 * relative base DN), or {@code null} if there are 1221 * none. 1222 * @param refinements 1223 * The optional specification filter refinements, or 1224 * {@code null} if there are none. 1225 */ 1226 public SubtreeSpecification(final DN rootDN, 1227 final DN relativeBaseDN, final int minimumDepth, 1228 final int maximumDepth, final Iterable<DN> chopBefore, 1229 final Iterable<DN> chopAfter, final Refinement refinements) 1230 { 1231 this.baseDN = relativeBaseDN == null ? rootDN : rootDN 1232 .child(relativeBaseDN); 1233 this.minimumDepth = minimumDepth; 1234 this.maximumDepth = maximumDepth; 1235 1236 if (chopBefore != null && chopBefore.iterator().hasNext()) 1237 { 1238 // Calculate the absolute DNs. 1239 final TreeMap<DN, DN> map = new TreeMap<>(); 1240 for (final DN localName : chopBefore) 1241 { 1242 map.put(baseDN.child(localName), localName); 1243 } 1244 this.chopBefore = Collections.unmodifiableMap(map); 1245 } 1246 else 1247 { 1248 // No chop before specifications. 1249 this.chopBefore = Collections.emptyMap(); 1250 } 1251 1252 if (chopAfter != null && chopAfter.iterator().hasNext()) 1253 { 1254 // Calculate the absolute DNs. 1255 final TreeMap<DN, DN> map = new TreeMap<>(); 1256 for (final DN localName : chopAfter) 1257 { 1258 map.put(baseDN.child(localName), localName); 1259 } 1260 this.chopAfter = Collections.unmodifiableMap(map); 1261 } 1262 else 1263 { 1264 // No chop after specifications. 1265 this.chopAfter = Collections.emptyMap(); 1266 } 1267 1268 this.rootDN = rootDN; 1269 this.relativeBaseDN = relativeBaseDN; 1270 this.refinements = refinements; 1271 } 1272 1273 1274 1275 /** 1276 * Indicates whether the provided object is logically equal to this 1277 * subtree specification object. 1278 * 1279 * @param obj 1280 * The object for which to make the determination. 1281 * @return {@code true} if the provided object is logically equal 1282 * to this subtree specification object, or {@code false} 1283 * if not. 1284 */ 1285 @Override 1286 public boolean equals(final Object obj) 1287 { 1288 1289 if (this == obj) 1290 { 1291 return true; 1292 } 1293 1294 if (obj instanceof SubtreeSpecification) 1295 { 1296 final SubtreeSpecification other = (SubtreeSpecification) obj; 1297 1298 if (!commonComponentsEquals(other)) 1299 { 1300 return false; 1301 } 1302 1303 if (!getBaseDN().equals(other.getBaseDN())) 1304 { 1305 return false; 1306 } 1307 1308 if (refinements != null) 1309 { 1310 return refinements.equals(other.refinements); 1311 } 1312 else 1313 { 1314 return refinements == other.refinements; 1315 } 1316 } 1317 1318 return false; 1319 } 1320 1321 1322 1323 /** 1324 * Get the absolute base DN of the subtree specification. 1325 * 1326 * @return Returns the absolute base DN of the subtree 1327 * specification. 1328 */ 1329 public DN getBaseDN() 1330 { 1331 return baseDN; 1332 } 1333 1334 1335 1336 /** 1337 * Get the set of chop after relative DNs. 1338 * 1339 * @return Returns the set of chop after relative DNs. 1340 */ 1341 public Iterable<DN> getChopAfter() 1342 { 1343 1344 return chopAfter.values(); 1345 } 1346 1347 1348 1349 /** 1350 * Get the set of chop before relative DNs. 1351 * 1352 * @return Returns the set of chop before relative DNs. 1353 */ 1354 public Iterable<DN> getChopBefore() 1355 { 1356 1357 return chopBefore.values(); 1358 } 1359 1360 1361 1362 /** 1363 * Get the maximum depth of the subtree specification. 1364 * 1365 * @return Returns the maximum depth (less than 0 indicates unlimited depth). 1366 */ 1367 public int getMaximumDepth() 1368 { 1369 return maximumDepth; 1370 } 1371 1372 1373 1374 /** 1375 * Get the minimum depth of the subtree specification. 1376 * 1377 * @return Returns the minimum depth (<=0 indicates unlimited 1378 * depth). 1379 */ 1380 public int getMinimumDepth() 1381 { 1382 return minimumDepth; 1383 } 1384 1385 1386 1387 /** 1388 * Get the specification filter refinements. 1389 * 1390 * @return Returns the specification filter refinements, or 1391 * <code>null</code> if none were specified. 1392 */ 1393 public Refinement getRefinements() 1394 { 1395 return refinements; 1396 } 1397 1398 1399 1400 /** 1401 * Get the relative base DN. 1402 * 1403 * @return Returns the relative base DN or <code>null</code> if 1404 * none was specified. 1405 */ 1406 public DN getRelativeBaseDN() 1407 { 1408 return relativeBaseDN; 1409 } 1410 1411 1412 1413 /** 1414 * Get the root DN. 1415 * 1416 * @return Returns the root DN. 1417 */ 1418 public DN getRootDN() 1419 { 1420 return rootDN; 1421 } 1422 1423 1424 1425 /** 1426 * Retrieves the hash code for this subtree specification object. 1427 * 1428 * @return The hash code for this subtree specification object. 1429 */ 1430 @Override 1431 public int hashCode() 1432 { 1433 1434 int hash = commonComponentsHashCode(); 1435 1436 hash = hash * 31 + getBaseDN().hashCode(); 1437 1438 if (refinements != null) 1439 { 1440 hash = hash * 31 + refinements.hashCode(); 1441 } 1442 1443 return hash; 1444 } 1445 1446 1447 1448 /** 1449 * Determine if the specified DN is within the scope of the subtree 1450 * specification. 1451 * 1452 * @param dn 1453 * The distinguished name. 1454 * @return Returns <code>true</code> if the DN is within the scope 1455 * of the subtree specification, or <code>false</code> 1456 * otherwise. 1457 */ 1458 public boolean isDNWithinScope(final DN dn) 1459 { 1460 1461 if (!dn.isDescendantOf(baseDN)) 1462 { 1463 return false; 1464 } 1465 1466 // Check minimum and maximum depths. 1467 final int baseRDNCount = baseDN.size(); 1468 1469 if (minimumDepth > 0) 1470 { 1471 final int entryRDNCount = dn.size(); 1472 1473 if (entryRDNCount - baseRDNCount < minimumDepth) 1474 { 1475 return false; 1476 } 1477 } 1478 1479 if (maximumDepth >= 0) 1480 { 1481 final int entryRDNCount = dn.size(); 1482 1483 if (entryRDNCount - baseRDNCount > maximumDepth) 1484 { 1485 return false; 1486 } 1487 } 1488 1489 // Check exclusions. 1490 for (final DN chopBeforeDN : chopBefore.keySet()) 1491 { 1492 if (dn.isDescendantOf(chopBeforeDN)) 1493 { 1494 return false; 1495 } 1496 } 1497 1498 for (final DN chopAfterDN : chopAfter.keySet()) 1499 { 1500 if (!dn.equals(chopAfterDN) && dn.isDescendantOf(chopAfterDN)) 1501 { 1502 return false; 1503 } 1504 } 1505 1506 // Everything seemed to match. 1507 return true; 1508 } 1509 1510 1511 1512 /** 1513 * Determine if an entry is within the scope of the subtree 1514 * specification. 1515 * 1516 * @param entry 1517 * The entry. 1518 * @return {@code true} if the entry is within the scope of the 1519 * subtree specification, or {@code false} if not. 1520 */ 1521 public boolean isWithinScope(final Entry entry) 1522 { 1523 return isDNWithinScope(entry.getName()) 1524 && (refinements == null || refinements.matches(entry)); 1525 } 1526 1527 1528 1529 /** 1530 * Retrieves a string representation of this subtree specification 1531 * object. 1532 * 1533 * @return A string representation of this subtree specification 1534 * object. 1535 */ 1536 @Override 1537 public String toString() 1538 { 1539 final StringBuilder builder = new StringBuilder(); 1540 return toString(builder).toString(); 1541 } 1542 1543 1544 1545 /** 1546 * Append the string representation of the subtree specification to 1547 * the provided string builder. 1548 * 1549 * @param builder 1550 * The string builder. 1551 * @return The string builder. 1552 */ 1553 public StringBuilder toString(final StringBuilder builder) 1554 { 1555 1556 boolean isFirstElement = true; 1557 1558 // Output the optional base DN. 1559 builder.append("{"); 1560 if (relativeBaseDN != null && !relativeBaseDN.isRootDN()) 1561 { 1562 builder.append(" base "); 1563 StaticUtils.toRFC3641StringValue(builder, 1564 relativeBaseDN.toString()); 1565 isFirstElement = false; 1566 } 1567 1568 // Output the optional specific exclusions. 1569 final Iterable<DN> chopBefore = getChopBefore(); 1570 final Iterable<DN> chopAfter = getChopAfter(); 1571 1572 if (chopBefore.iterator().hasNext() 1573 || chopAfter.iterator().hasNext()) 1574 { 1575 if (!isFirstElement) 1576 { 1577 builder.append(","); 1578 } 1579 else 1580 { 1581 isFirstElement = false; 1582 } 1583 builder.append(" specificExclusions { "); 1584 1585 boolean isFirst = true; 1586 1587 for (final DN dn : chopBefore) 1588 { 1589 if (!isFirst) 1590 { 1591 builder.append(", chopBefore:"); 1592 } 1593 else 1594 { 1595 builder.append("chopBefore:"); 1596 isFirst = false; 1597 } 1598 StaticUtils.toRFC3641StringValue(builder, dn.toString()); 1599 } 1600 1601 for (final DN dn : chopAfter) 1602 { 1603 if (!isFirst) 1604 { 1605 builder.append(", chopAfter:"); 1606 } 1607 else 1608 { 1609 builder.append("chopAfter:"); 1610 isFirst = false; 1611 } 1612 StaticUtils.toRFC3641StringValue(builder, dn.toString()); 1613 } 1614 1615 builder.append(" }"); 1616 } 1617 1618 // Output the optional minimum depth. 1619 if (getMinimumDepth() > 0) 1620 { 1621 if (!isFirstElement) 1622 { 1623 builder.append(","); 1624 } 1625 else 1626 { 1627 isFirstElement = false; 1628 } 1629 builder.append(" minimum "); 1630 builder.append(getMinimumDepth()); 1631 } 1632 1633 // Output the optional maximum depth. 1634 if (getMaximumDepth() >= 0) 1635 { 1636 if (!isFirstElement) 1637 { 1638 builder.append(","); 1639 } 1640 else 1641 { 1642 isFirstElement = false; 1643 } 1644 builder.append(" maximum "); 1645 builder.append(getMaximumDepth()); 1646 } 1647 1648 // Output the optional refinements. 1649 if (refinements != null) 1650 { 1651 if (!isFirstElement) 1652 { 1653 builder.append(","); 1654 } 1655 else 1656 { 1657 isFirstElement = false; 1658 } 1659 builder.append(" specificationFilter "); 1660 refinements.toString(builder); 1661 } 1662 1663 builder.append(" }"); 1664 1665 return builder; 1666 } 1667 1668 1669 1670 /** 1671 * Determine if the common components of this subtree specification 1672 * are equal to the common components of another subtree 1673 * specification. 1674 * 1675 * @param other 1676 * The other subtree specification. 1677 * @return Returns <code>true</code> if they are equal. 1678 */ 1679 private boolean commonComponentsEquals( 1680 final SubtreeSpecification other) 1681 { 1682 if (this == other) 1683 { 1684 return true; 1685 } 1686 return minimumDepth == other.minimumDepth 1687 && maximumDepth == other.maximumDepth 1688 && chopBefore.values().equals(other.chopBefore.values()) 1689 && chopAfter.values().equals(other.chopAfter.values()); 1690 } 1691 1692 1693 1694 /** 1695 * Get a hash code of the subtree specification's common components. 1696 * 1697 * @return The computed hash code. 1698 */ 1699 private int commonComponentsHashCode() 1700 { 1701 int hash = minimumDepth * 31 + maximumDepth; 1702 hash = hash * 31 + chopBefore.values().hashCode(); 1703 hash = hash * 31 + chopAfter.values().hashCode(); 1704 return hash; 1705 } 1706}