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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.core; 028 import org.opends.messages.Message; 029 030 import static org.opends.messages.SchemaMessages.*; 031 032 import java.util.ArrayList; 033 import java.util.Collection; 034 import java.util.HashSet; 035 import java.util.InputMismatchException; 036 import java.util.Iterator; 037 import java.util.NoSuchElementException; 038 039 import org.opends.server.types.DirectoryException; 040 import org.opends.server.types.DN; 041 import org.opends.server.types.Entry; 042 import org.opends.server.types.ObjectClass; 043 import org.opends.server.types.ResultCode; 044 import org.opends.server.util.StaticUtils; 045 046 /** 047 * An RFC 3672 subtree specification. 048 * <p> 049 * Refer to RFC 3672 for a detailed definition of the subtree 050 * specification string representation. 051 */ 052 public final class RFC3672SubtreeSpecification extends 053 SimpleSubtreeSpecification { 054 055 // The root DN. 056 private DN rootDN; 057 058 // The optional relative base DN. 059 private DN relativeBaseDN; 060 061 // The optional specification filter refinements. 062 private Refinement refinements; 063 064 /** 065 * Abstract interface for RFC3672 specification filter refinements. 066 */ 067 public static abstract class Refinement { 068 /** 069 * Create a new RFC3672 specification filter refinement. 070 */ 071 protected Refinement() { 072 // No implementation required. 073 } 074 075 /** 076 * Check if the refinement matches the given entry. 077 * 078 * @param entry 079 * The filterable entry. 080 * @return Returns <code>true</code> if the entry matches the 081 * refinement, or <code>false</code> otherwise. 082 */ 083 public abstract boolean matches(Entry entry); 084 085 /** 086 * {@inheritDoc} 087 */ 088 @Override 089 public final String toString() { 090 StringBuilder builder = new StringBuilder(); 091 092 return toString(builder).toString(); 093 } 094 095 /** 096 * Append the string representation of the refinement to the 097 * provided string builder. 098 * 099 * @param builder 100 * The string builder. 101 * @return The string builder. 102 */ 103 public abstract StringBuilder toString(StringBuilder builder); 104 105 /** 106 * {@inheritDoc} 107 */ 108 @Override 109 public abstract boolean equals(Object obj); 110 111 /** 112 * {@inheritDoc} 113 */ 114 @Override 115 public abstract int hashCode(); 116 } 117 118 /** 119 * RFC 3672 subtree specification Item refinement. This type of 120 * refinement filters entries based on the presence of a specified 121 * object class. 122 */ 123 public static final class ItemRefinement extends Refinement { 124 // The item's object class. 125 private String objectClass; 126 127 // The item's normalized object class. 128 private String normalizedObjectClass; 129 130 /** 131 * Create a new item refinement. 132 * 133 * @param objectClass 134 * The item's object class. 135 */ 136 public ItemRefinement(String objectClass) { 137 138 this.objectClass = objectClass; 139 this.normalizedObjectClass = StaticUtils.toLowerCase(objectClass 140 .trim()); 141 } 142 143 /** 144 * {@inheritDoc} 145 */ 146 @Override 147 public boolean matches(Entry entry) { 148 ObjectClass oc = DirectoryServer 149 .getObjectClass(normalizedObjectClass); 150 151 if (oc == null) { 152 return false; 153 } else { 154 return entry.hasObjectClass(oc); 155 } 156 } 157 158 /** 159 * {@inheritDoc} 160 */ 161 @Override 162 public StringBuilder toString(StringBuilder builder) { 163 builder.append("item:"); 164 builder.append(objectClass); 165 return builder; 166 } 167 168 /** 169 * {@inheritDoc} 170 */ 171 @Override 172 public boolean equals(Object obj) { 173 174 if (this == obj) { 175 return true; 176 } 177 178 if (obj instanceof ItemRefinement) { 179 ItemRefinement other = (ItemRefinement) obj; 180 181 return normalizedObjectClass.equals(other.normalizedObjectClass); 182 } 183 184 return false; 185 } 186 187 /** 188 * {@inheritDoc} 189 */ 190 @Override 191 public int hashCode() { 192 193 return normalizedObjectClass.hashCode(); 194 } 195 } 196 197 /** 198 * RFC 3672 subtree specification NOT refinement. This type of 199 * refinement filters entries based on the underlying refinement being 200 * <code>false</code>. 201 */ 202 public static final class NotRefinement extends Refinement { 203 // The inverted refinement. 204 private Refinement refinement; 205 206 /** 207 * Create a new NOT refinement. 208 * 209 * @param refinement 210 * The refinement which must be <code>false</code>. 211 */ 212 public NotRefinement(Refinement refinement) { 213 214 this.refinement = refinement; 215 } 216 217 /** 218 * {@inheritDoc} 219 */ 220 @Override 221 public boolean matches(Entry entry) { 222 return !refinement.matches(entry); 223 } 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override 229 public StringBuilder toString(StringBuilder builder) { 230 builder.append("not:"); 231 return refinement.toString(builder); 232 } 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 public boolean equals(Object obj) { 239 240 if (this == obj) { 241 return true; 242 } 243 244 if (obj instanceof NotRefinement) { 245 NotRefinement other = (NotRefinement) obj; 246 247 return refinement.equals(other.refinement); 248 } 249 250 return false; 251 } 252 253 /** 254 * {@inheritDoc} 255 */ 256 @Override 257 public int hashCode() { 258 259 return refinement.hashCode(); 260 } 261 } 262 263 /** 264 * RFC 3672 subtree specification AND refinement. This type of 265 * refinement filters entries based on all of the underlying 266 * refinements being <code>true</code>. 267 */ 268 public static final class AndRefinement extends Refinement { 269 // The set of refinements which must all be true. 270 private Collection<Refinement> refinementSet; 271 272 /** 273 * Create a new AND refinement. 274 * 275 * @param refinementSet 276 * The set of refinements which must all be 277 * <code>true</code>. 278 */ 279 public AndRefinement(Collection<Refinement> refinementSet) { 280 281 this.refinementSet = refinementSet; 282 } 283 284 /** 285 * {@inheritDoc} 286 */ 287 @Override 288 public boolean matches(Entry entry) { 289 for (Refinement refinement : refinementSet) { 290 if (refinement.matches(entry) == false) { 291 return false; 292 } 293 } 294 295 // All sub-refinements matched. 296 return true; 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override 303 public StringBuilder toString(StringBuilder builder) { 304 switch (refinementSet.size()) { 305 case 0: 306 // Do nothing. 307 break; 308 case 1: 309 refinementSet.iterator().next().toString(builder); 310 break; 311 default: 312 builder.append("and:{"); 313 Iterator<Refinement> iterator = refinementSet.iterator(); 314 iterator.next().toString(builder); 315 while (iterator.hasNext()) { 316 builder.append(", "); 317 iterator.next().toString(builder); 318 } 319 builder.append("}"); 320 break; 321 } 322 323 return builder; 324 } 325 326 /** 327 * {@inheritDoc} 328 */ 329 @Override 330 public boolean equals(Object obj) { 331 332 if (this == obj) { 333 return true; 334 } 335 336 if (obj instanceof AndRefinement) { 337 AndRefinement other = (AndRefinement) obj; 338 339 return refinementSet.equals(other.refinementSet); 340 } 341 342 return false; 343 } 344 345 /** 346 * {@inheritDoc} 347 */ 348 @Override 349 public int hashCode() { 350 351 return refinementSet.hashCode(); 352 } 353 } 354 355 /** 356 * RFC 3672 subtree specification OR refinement. This type of 357 * refinement filters entries based on at least one of the underlying 358 * refinements being <code>true</code>. 359 */ 360 public static final class OrRefinement extends Refinement { 361 // The set of refinements of which at least one must be true. 362 private Collection<Refinement> refinementSet; 363 364 /** 365 * Create a new OR refinement. 366 * 367 * @param refinementSet 368 * The set of refinements of which at least one must be 369 * <code>true</code>. 370 */ 371 public OrRefinement(Collection<Refinement> refinementSet) { 372 373 this.refinementSet = refinementSet; 374 } 375 376 /** 377 * {@inheritDoc} 378 */ 379 @Override 380 public boolean matches(Entry entry) { 381 for (Refinement refinement : refinementSet) { 382 if (refinement.matches(entry) == true) { 383 return true; 384 } 385 } 386 387 // No sub-refinements matched. 388 return false; 389 } 390 391 /** 392 * {@inheritDoc} 393 */ 394 @Override 395 public StringBuilder toString(StringBuilder builder) { 396 switch (refinementSet.size()) { 397 case 0: 398 // Do nothing. 399 break; 400 case 1: 401 refinementSet.iterator().next().toString(builder); 402 break; 403 default: 404 builder.append("or:{"); 405 Iterator<Refinement> iterator = refinementSet.iterator(); 406 iterator.next().toString(builder); 407 while (iterator.hasNext()) { 408 builder.append(", "); 409 iterator.next().toString(builder); 410 } 411 builder.append("}"); 412 break; 413 } 414 415 return builder; 416 } 417 418 /** 419 * {@inheritDoc} 420 */ 421 @Override 422 public boolean equals(Object obj) { 423 424 if (this == obj) { 425 return true; 426 } 427 428 if (obj instanceof AndRefinement) { 429 AndRefinement other = (AndRefinement) obj; 430 431 return refinementSet.equals(other.refinementSet); 432 } 433 434 return false; 435 } 436 437 /** 438 * {@inheritDoc} 439 */ 440 @Override 441 public int hashCode() { 442 443 return refinementSet.hashCode(); 444 } 445 } 446 447 /** 448 * Parses the string argument as an RFC3672 subtree specification. 449 * 450 * @param rootDN 451 * The DN of the subtree specification's base entry. 452 * @param s 453 * The string to be parsed. 454 * @return The RFC3672 subtree specification represented by the string 455 * argument. 456 * @throws DirectoryException 457 * If the string does not contain a parsable relative 458 * subtree specification. 459 */ 460 public static RFC3672SubtreeSpecification valueOf(DN rootDN, String s) 461 throws DirectoryException { 462 463 // Default values. 464 DN relativeBaseDN = null; 465 466 int minimum = -1; 467 int maximum = -1; 468 469 HashSet<DN> chopBefore = new HashSet<DN>(); 470 HashSet<DN> chopAfter = new HashSet<DN>(); 471 472 Refinement refinement = null; 473 474 // Value must have an opening left brace. 475 Parser parser = new Parser(s); 476 boolean isValid = true; 477 478 try { 479 parser.skipLeftBrace(); 480 481 // Parse each element of the value sequence. 482 boolean isFirst = true; 483 484 while (true) { 485 if (parser.hasNextRightBrace()) { 486 // Make sure that there is a closing brace and no trailing 487 // text. 488 parser.skipRightBrace(); 489 490 if (parser.hasNext()) { 491 throw new java.util.InputMismatchException(); 492 } 493 break; 494 } 495 496 // Make sure that there is a comma separator if this is not the 497 // first element. 498 if (!isFirst) { 499 parser.skipSeparator(); 500 } else { 501 isFirst = false; 502 } 503 504 String key = parser.nextKey(); 505 if (key.equals("base")) { 506 if (relativeBaseDN != null) { 507 // Relative base DN specified more than once. 508 throw new InputMismatchException(); 509 } 510 relativeBaseDN = DN.decode(parser.nextStringValue()); 511 } else if (key.equals("minimum")) { 512 if (minimum != -1) { 513 // Minimum specified more than once. 514 throw new InputMismatchException(); 515 } 516 minimum = parser.nextInt(); 517 } else if (key.equals("maximum")) { 518 if (maximum != -1) { 519 // Maximum specified more than once. 520 throw new InputMismatchException(); 521 } 522 maximum = parser.nextInt(); 523 } else if (key.equals("specificationfilter")) { 524 if (refinement != null) { 525 // Refinements specified more than once. 526 throw new InputMismatchException(); 527 } 528 529 refinement = parseRefinement(parser); 530 } else if (key.equals("specificexclusions")) { 531 if (!chopBefore.isEmpty() || !chopAfter.isEmpty()) { 532 // Specific exclusions specified more than once. 533 throw new InputMismatchException(); 534 } 535 536 parser.nextSpecificExclusions(chopBefore, chopAfter); 537 } else { 538 throw new InputMismatchException(); 539 } 540 } 541 542 // Make default minimum value is 0. 543 if (minimum < 0) { 544 minimum = 0; 545 } 546 547 // Check that the maximum, if specified, is gte the minimum. 548 if (maximum >= 0 && maximum < minimum) { 549 isValid = false; 550 } 551 } catch (InputMismatchException e) { 552 isValid = false; 553 } catch (NoSuchElementException e) { 554 isValid = false; 555 } 556 557 if (isValid) { 558 return new RFC3672SubtreeSpecification(rootDN, relativeBaseDN, 559 minimum, maximum, chopBefore, chopAfter, refinement); 560 } else { 561 Message message = 562 ERR_ATTR_SYNTAX_RFC3672_SUBTREE_SPECIFICATION_INVALID.get(s); 563 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 564 message); 565 } 566 } 567 568 /** 569 * Parse a single refinement. 570 * 571 * @param parser 572 * The active subtree specification parser. 573 * @return The parsed refinement. 574 * @throws InputMismatchException 575 * If the common component did not have a valid syntax. 576 * @throws NoSuchElementException 577 * If input is exhausted. 578 */ 579 private static Refinement parseRefinement(Parser parser) 580 throws InputMismatchException, NoSuchElementException { 581 // Get the type of refinement. 582 String type = StaticUtils.toLowerCase(parser.nextName()); 583 584 // Skip the colon separator. 585 parser.skipColon(); 586 587 if (type.equals("item")) { 588 return new ItemRefinement(parser.nextName()); 589 } else if (type.equals("not")) { 590 Refinement refinement = parseRefinement(parser); 591 return new NotRefinement(refinement); 592 } else if (type.equals("and")) { 593 ArrayList<Refinement> refinements = parseRefinementSet(parser); 594 return new AndRefinement(refinements); 595 } else if (type.equals("or")) { 596 ArrayList<Refinement> refinements = parseRefinementSet(parser); 597 return new OrRefinement(refinements); 598 } else { 599 // Unknown refinement type. 600 throw new InputMismatchException(); 601 } 602 } 603 604 /** 605 * Parse a list of refinements. 606 * 607 * @param parser 608 * The active subtree specification parser. 609 * @return The parsed refinement list. 610 * @throws InputMismatchException 611 * If the common component did not have a valid syntax. 612 * @throws NoSuchElementException 613 * If input is exhausted. 614 */ 615 private static ArrayList<Refinement> parseRefinementSet(Parser parser) 616 throws InputMismatchException, NoSuchElementException { 617 ArrayList<Refinement> refinements = new ArrayList<Refinement>(); 618 619 // Skip leading open-brace. 620 parser.skipLeftBrace(); 621 622 // Parse each chop DN in the sequence. 623 boolean isFirstValue = true; 624 while (true) { 625 // Make sure that there is a closing brace. 626 if (parser.hasNextRightBrace()) { 627 parser.skipRightBrace(); 628 break; 629 } 630 631 // Make sure that there is a comma separator if this is not 632 // the first element. 633 if (!isFirstValue) { 634 parser.skipSeparator(); 635 } else { 636 isFirstValue = false; 637 } 638 639 // Parse each sub-refinement. 640 Refinement refinement = parseRefinement(parser); 641 refinements.add(refinement); 642 } 643 644 return refinements; 645 } 646 647 /** 648 * Create a new RFC3672 subtree specification. 649 * 650 * @param rootDN 651 * The root DN of the subtree. 652 * @param relativeBaseDN 653 * The relative base DN (or <code>null</code> if not 654 * specified). 655 * @param minimumDepth 656 * The minimum depth (<=0 means unlimited). 657 * @param maximumDepth 658 * The maximum depth (<0 means unlimited). 659 * @param chopBefore 660 * The set of chop before local names (relative to the 661 * relative base DN), or <code>null</code> if there are 662 * none. 663 * @param chopAfter 664 * The set of chop after local names (relative to the 665 * relative base DN), or <code>null</code> if there are 666 * none. 667 * @param refinements 668 * The optional specification filter refinements, or 669 * <code>null</code> if there are none. 670 */ 671 public RFC3672SubtreeSpecification(DN rootDN, DN relativeBaseDN, 672 int minimumDepth, int maximumDepth, Iterable<DN> chopBefore, 673 Iterable<DN> chopAfter, Refinement refinements) { 674 super(relativeBaseDN == null ? rootDN : rootDN.concat(relativeBaseDN), 675 minimumDepth, maximumDepth, chopBefore, chopAfter); 676 677 678 this.rootDN = rootDN; 679 this.relativeBaseDN = relativeBaseDN; 680 this.refinements = refinements; 681 } 682 683 /** 684 * Get the root DN. 685 * 686 * @return Returns the root DN. 687 */ 688 public DN getRootDN() { 689 return rootDN; 690 } 691 692 /** 693 * Get the relative base DN. 694 * 695 * @return Returns the relative base DN or <code>null</code> if none 696 * was specified. 697 */ 698 public DN getRelativeBaseDN() { 699 return relativeBaseDN; 700 } 701 702 /** 703 * Get the specification filter refinements. 704 * 705 * @return Returns the specification filter refinements, or 706 * <code>null</code> if none were specified. 707 */ 708 public Refinement getRefinements() { 709 return refinements; 710 } 711 712 /** 713 * {@inheritDoc} 714 */ 715 @Override 716 public boolean isWithinScope(Entry entry) { 717 718 if (isDNWithinScope(entry.getDN())) { 719 if (refinements != null) { 720 return refinements.matches(entry); 721 } else { 722 return true; 723 } 724 } else { 725 return false; 726 } 727 } 728 729 /** 730 * {@inheritDoc} 731 */ 732 @Override 733 public StringBuilder toString(StringBuilder builder) { 734 735 boolean isFirstElement = true; 736 737 // Output the optional base DN. 738 builder.append("{"); 739 if (relativeBaseDN != null && !relativeBaseDN.isNullDN()) { 740 builder.append(" base "); 741 StaticUtils.toRFC3641StringValue(builder, relativeBaseDN.toString()); 742 isFirstElement = false; 743 } 744 745 // Output the optional specific exclusions. 746 Iterable<DN> chopBefore = getChopBefore(); 747 Iterable<DN> chopAfter = getChopAfter(); 748 749 if ((chopBefore != null && chopBefore.iterator().hasNext()) 750 || (chopAfter != null && chopAfter.iterator().hasNext())) { 751 752 if (!isFirstElement) { 753 builder.append(","); 754 } else { 755 isFirstElement = false; 756 } 757 builder.append(" specificExclusions { "); 758 759 boolean isFirst = true; 760 761 if (chopBefore != null) { 762 for (DN dn : chopBefore) { 763 if (!isFirst) { 764 builder.append(", chopBefore:"); 765 } else { 766 builder.append("chopBefore:"); 767 isFirst = false; 768 } 769 StaticUtils.toRFC3641StringValue(builder, dn.toString()); 770 } 771 } 772 773 if (chopAfter != null) { 774 for (DN dn : chopAfter) { 775 if (!isFirst) { 776 builder.append(", chopAfter:"); 777 } else { 778 builder.append("chopAfter:"); 779 isFirst = false; 780 } 781 StaticUtils.toRFC3641StringValue(builder, dn.toString()); 782 } 783 } 784 785 builder.append(" }"); 786 } 787 788 // Output the optional minimum depth. 789 if (getMinimumDepth() > 0) { 790 if (!isFirstElement) { 791 builder.append(","); 792 } else { 793 isFirstElement = false; 794 } 795 builder.append(" minimum "); 796 builder.append(getMinimumDepth()); 797 } 798 799 // Output the optional maximum depth. 800 if (getMaximumDepth() >= 0) { 801 if (!isFirstElement) { 802 builder.append(","); 803 } else { 804 isFirstElement = false; 805 } 806 builder.append(" maximum "); 807 builder.append(getMaximumDepth()); 808 } 809 810 // Output the optional refinements. 811 if (refinements != null) { 812 if (!isFirstElement) { 813 builder.append(","); 814 } else { 815 isFirstElement = false; 816 } 817 builder.append(" specificationFilter "); 818 refinements.toString(builder); 819 } 820 821 builder.append(" }"); 822 823 return builder; 824 } 825 826 /** 827 * {@inheritDoc} 828 */ 829 @Override 830 public boolean equals(Object obj) { 831 832 if (this == obj) { 833 return true; 834 } 835 836 if (obj instanceof RFC3672SubtreeSpecification) { 837 RFC3672SubtreeSpecification other = (RFC3672SubtreeSpecification) obj; 838 839 if (!commonComponentsEquals(other)) { 840 return false; 841 } 842 843 if (!getBaseDN().equals(other.getBaseDN())) { 844 return false; 845 } 846 847 if (refinements != null) { 848 return refinements.equals(other.refinements); 849 } else { 850 return refinements == other.refinements; 851 } 852 } 853 854 return false; 855 } 856 857 /** 858 * {@inheritDoc} 859 */ 860 @Override 861 public int hashCode() { 862 863 int hash = commonComponentsHashCode(); 864 865 hash = hash * 31 + getBaseDN().hashCode(); 866 867 if (refinements != null) { 868 hash = hash * 31 + refinements.hashCode(); 869 } 870 871 return hash; 872 } 873 }