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    }