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 }