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    
029    import java.util.InputMismatchException;
030    import java.util.NoSuchElementException;
031    import java.util.Scanner;
032    import java.util.Set;
033    import java.util.TreeMap;
034    import java.util.regex.Pattern;
035    
036    import org.opends.server.api.SubtreeSpecification;
037    import org.opends.server.types.DirectoryException;
038    import org.opends.server.types.DN;
039    import org.opends.server.util.StaticUtils;
040    
041    /**
042     * A simple subtree specification implementation that has a subtree
043     * base, optional minimum and maximum depths, and a set of chop
044     * specifications.
045     */
046    public abstract class SimpleSubtreeSpecification extends
047        SubtreeSpecification {
048    
049    
050      // The absolute base of the subtree.
051      private DN baseDN;
052    
053      // Optional minimum depth (<=0 means unlimited).
054      private int minimumDepth;
055    
056      // Optional maximum depth (<0 means unlimited).
057      private int maximumDepth;
058    
059      // Optional set of chop before absolute DNs (mapping to their
060      // local-names).
061      private TreeMap<DN, DN> chopBefore;
062    
063      // Optional set of chop after absolute DNs (mapping to their
064      // local-names).
065      private TreeMap<DN, DN> chopAfter;
066    
067      /**
068       * Internal utility class which can be used by sub-classes to help
069       * parse string representations.
070       */
071      protected static final class Parser {
072        // Text scanner used to parse the string value.
073        private Scanner scanner;
074    
075        // Pattern used to detect left braces.
076        private static Pattern LBRACE = Pattern.compile("\\{.*");
077    
078        // Pattern used to parse left braces.
079        private static Pattern LBRACE_TOKEN = Pattern.compile("\\{");
080    
081        // Pattern used to detect right braces.
082        private static Pattern RBRACE = Pattern.compile("\\}.*");
083    
084        // Pattern used to parse right braces.
085        private static Pattern RBRACE_TOKEN = Pattern.compile("\\}");
086    
087        // Pattern used to detect comma separators.
088        private static Pattern SEP = Pattern.compile(",.*");
089    
090        // Pattern used to parse comma separators.
091        private static Pattern SEP_TOKEN = Pattern.compile(",");
092    
093        // Pattern used to detect colon separators.
094        private static Pattern COLON = Pattern.compile(":.*");
095    
096        // Pattern used to parse colon separators.
097        private static Pattern COLON_TOKEN = Pattern.compile(":");
098    
099        // Pattern used to detect integer values.
100        private static Pattern INT = Pattern.compile("\\d.*");
101    
102        // Pattern used to parse integer values.
103        private static Pattern INT_TOKEN = Pattern.compile("\\d+");
104    
105        // Pattern used to detect name values.
106        private static Pattern NAME = Pattern.compile("[\\w_;-].*");
107    
108        // Pattern used to parse name values.
109        private static Pattern NAME_TOKEN = Pattern.compile("[\\w_;-]+");
110    
111        // Pattern used to detect RFC3641 string values.
112        private static Pattern STRING_VALUE = Pattern.compile("\".*");
113    
114        // Pattern used to parse RFC3641 string values.
115        private static Pattern STRING_VALUE_TOKEN = Pattern
116            .compile("\"([^\"]|(\"\"))*\"");
117    
118        /**
119         * Create a new parser for a subtree specification string value.
120         *
121         * @param value
122         *          The subtree specification string value.
123         */
124        public Parser(String value) {
125          this.scanner = new Scanner(value);
126        }
127    
128        /**
129         * Skip a left-brace character.
130         *
131         * @throws InputMismatchException
132         *           If the next token is not a left-brace character.
133         * @throws NoSuchElementException
134         *           If input is exhausted.
135         */
136        public void skipLeftBrace() throws InputMismatchException,
137            NoSuchElementException {
138          nextValue(LBRACE, LBRACE_TOKEN);
139        }
140    
141        /**
142         * Skip a right-brace character.
143         *
144         * @throws InputMismatchException
145         *           If the next token is not a right-brace character.
146         * @throws NoSuchElementException
147         *           If input is exhausted.
148         */
149        public void skipRightBrace() throws InputMismatchException,
150            NoSuchElementException {
151          nextValue(RBRACE, RBRACE_TOKEN);
152        }
153    
154        /**
155         * Skip a comma separator.
156         *
157         * @throws InputMismatchException
158         *           If the next token is not a comma separator character.
159         * @throws NoSuchElementException
160         *           If input is exhausted.
161         */
162        public void skipSeparator() throws InputMismatchException,
163            NoSuchElementException {
164          nextValue(SEP, SEP_TOKEN);
165        }
166    
167        /**
168         * Skip a colon separator.
169         *
170         * @throws InputMismatchException
171         *           If the next token is not a colon separator character.
172         * @throws NoSuchElementException
173         *           If input is exhausted.
174         */
175        public void skipColon() throws InputMismatchException,
176            NoSuchElementException {
177          nextValue(COLON, COLON_TOKEN);
178        }
179    
180        /**
181         * Determine if the next token is a right-brace character.
182         *
183         * @return <code>true</code> if and only if the next token is a
184         *         valid right brace character.
185         */
186        public boolean hasNextRightBrace() {
187          return scanner.hasNext(RBRACE);
188        }
189    
190        /**
191         * Determine if there are remaining tokens.
192         *
193         * @return <code>true</code> if and only if there are remaining
194         *         tokens.
195         */
196        public boolean hasNext() {
197          return scanner.hasNext();
198        }
199    
200        /**
201         * Scans the next token of the input as a key value.
202         *
203         * @return The lower-case key value scanned from the input.
204         * @throws InputMismatchException
205         *           If the next token is not a valid key string.
206         * @throws NoSuchElementException
207         *           If input is exhausted.
208         */
209        public String nextKey() throws InputMismatchException,
210            NoSuchElementException {
211          return StaticUtils.toLowerCase(scanner.next());
212        }
213    
214        /**
215         * Scans the next token of the input as a name value.
216         * <p>
217         * A name is any string containing only alpha-numeric characters or
218         * hyphens, semi-colons, or underscores.
219         *
220         * @return The name value scanned from the input.
221         * @throws InputMismatchException
222         *           If the next token is not a valid name string.
223         * @throws NoSuchElementException
224         *           If input is exhausted.
225         */
226        public String nextName() throws InputMismatchException,
227            NoSuchElementException {
228          return nextValue(NAME, NAME_TOKEN);
229        }
230    
231        /**
232         * Scans the next token of the input as a non-negative
233         * <code>int</code> value.
234         *
235         * @return The name value scanned from the input.
236         * @throws InputMismatchException
237         *           If the next token is not a valid non-negative integer
238         *           string.
239         * @throws NoSuchElementException
240         *           If input is exhausted.
241         */
242        public int nextInt() throws InputMismatchException,
243            NoSuchElementException {
244          String s = nextValue(INT, INT_TOKEN);
245          return Integer.parseInt(s);
246        }
247    
248        /**
249         * Scans the next token of the input as a string quoted according to
250         * the StringValue production in RFC 3641.
251         * <p>
252         * The return string has its outer double quotes removed and any
253         * escaped inner double quotes unescaped.
254         *
255         * @return The string value scanned from the input.
256         * @throws InputMismatchException
257         *           If the next token is not a valid string.
258         * @throws NoSuchElementException
259         *           If input is exhausted.
260         */
261        public String nextStringValue() throws InputMismatchException,
262            NoSuchElementException {
263          String s = nextValue(STRING_VALUE, STRING_VALUE_TOKEN);
264          return s.substring(1, s.length() - 1).replace("\"\"", "\"");
265        }
266    
267        /**
268         * Scans the next tokens of the input as a set of specific
269         * exclusions encoded according to the SpecificExclusion production
270         * in RFC 3672.
271         *
272         * @param chopBefore
273         *          The set of chop before local names.
274         * @param chopAfter
275         *          The set of chop after local names.
276         * @throws InputMismatchException
277         *           If the common component did not have a valid syntax.
278         * @throws NoSuchElementException
279         *           If input is exhausted.
280         * @throws DirectoryException
281         *           If an error occurred when attempting to parse a DN
282         *           value.
283         */
284        public void nextSpecificExclusions(Set<DN> chopBefore, Set<DN> chopAfter)
285            throws InputMismatchException, NoSuchElementException,
286            DirectoryException {
287    
288          // Skip leading open-brace.
289          skipLeftBrace();
290    
291          // Parse each chop DN in the sequence.
292          boolean isFirstValue = true;
293          while (true) {
294            // Make sure that there is a closing brace.
295            if (hasNextRightBrace()) {
296              skipRightBrace();
297              break;
298            }
299    
300            // Make sure that there is a comma separator if this is not
301            // the first element.
302            if (!isFirstValue) {
303              skipSeparator();
304            } else {
305              isFirstValue = false;
306            }
307    
308            // Parse each chop specification which is of the form
309            // <type>:<value>.
310            String type = StaticUtils.toLowerCase(nextName());
311            skipColon();
312            if (type.equals("chopbefore")) {
313              chopBefore.add(DN.decode(nextStringValue()));
314            } else if (type.equals("chopafter")) {
315              chopAfter.add(DN.decode(nextStringValue()));
316            } else {
317              throw new java.util.InputMismatchException();
318            }
319          }
320        }
321    
322        /**
323         * Parse the next token from the string using the specified
324         * patterns.
325         *
326         * @param head
327         *          The pattern used to determine if the next token is a
328         *          possible match.
329         * @param content
330         *          The pattern used to parse the token content.
331         * @return The next token matching the <code>content</code>
332         *         pattern.
333         * @throws InputMismatchException
334         *           If the next token does not match the
335         *           <code>content</code> pattern.
336         * @throws NoSuchElementException
337         *           If input is exhausted.
338         */
339        private String nextValue(Pattern head, Pattern content)
340            throws InputMismatchException, NoSuchElementException {
341          if (!scanner.hasNext()) {
342            throw new java.util.NoSuchElementException();
343          }
344    
345          if (!scanner.hasNext(head)) {
346            throw new java.util.InputMismatchException();
347          }
348    
349          String s = scanner.findInLine(content);
350          if (s == null) {
351            throw new java.util.InputMismatchException();
352          }
353    
354          return s;
355        }
356      }
357    
358      /**
359       * Create a new simple subtree specification.
360       *
361       * @param baseDN
362       *          The absolute base DN of the subtree.
363       * @param minimumDepth
364       *          The minimum depth (<=0 means unlimited).
365       * @param maximumDepth
366       *          The maximum depth (<0 means unlimited).
367       * @param chopBefore
368       *          The set of chop before local names (relative to the base
369       *          DN), or <code>null</code> if there are none.
370       * @param chopAfter
371       *          The set of chop after local names (relative to the base
372       *          DN), or <code>null</code> if there are none.
373       */
374      protected SimpleSubtreeSpecification(DN baseDN, int minimumDepth,
375          int maximumDepth, Iterable<DN> chopBefore, Iterable<DN> chopAfter) {
376    
377        this.baseDN = baseDN;
378        this.minimumDepth = minimumDepth;
379        this.maximumDepth = maximumDepth;
380    
381        if (chopBefore != null && chopBefore.iterator().hasNext()) {
382          // Calculate the absolute DNs.
383          this.chopBefore = new TreeMap<DN, DN>();
384    
385          for (DN localName : chopBefore) {
386            this.chopBefore.put(baseDN.concat(localName), localName);
387          }
388        } else {
389          // No chop before specifications.
390          this.chopBefore = null;
391        }
392    
393        if (chopAfter != null && chopAfter.iterator().hasNext()) {
394          // Calculate the absolute DNs.
395          this.chopAfter = new TreeMap<DN, DN>();
396    
397          for (DN localName : chopAfter) {
398            this.chopAfter.put(baseDN.concat(localName), localName);
399          }
400        } else {
401          // No chop after specifications.
402          this.chopAfter = null;
403        }
404      }
405    
406      /**
407       * Determine if the specified DN is within the scope of the subtree
408       * specification.
409       *
410       * @param dn
411       *          The distringuished name.
412       * @return Returns <code>true</code> if the DN is within the scope
413       *         of the subtree specification, or <code>false</code>
414       *         otherwise.
415       */
416      protected final boolean isDNWithinScope(DN dn) {
417    
418        if (!dn.isDescendantOf(baseDN)) {
419          return false;
420        }
421    
422        // Check minimum and maximum depths.
423        int baseRDNCount = baseDN.getNumComponents();
424    
425        if (minimumDepth > 0) {
426          int entryRDNCount = dn.getNumComponents();
427    
428          if (entryRDNCount - baseRDNCount < minimumDepth) {
429            return false;
430          }
431        }
432    
433        if (maximumDepth >= 0) {
434          int entryRDNCount = dn.getNumComponents();
435    
436          if (entryRDNCount - baseRDNCount > maximumDepth) {
437            return false;
438          }
439        }
440    
441        // Check exclusions.
442        if (chopBefore != null) {
443          for (DN chopBeforeDN : chopBefore.keySet()) {
444            if (dn.isDescendantOf(chopBeforeDN)) {
445              return false;
446            }
447          }
448        }
449    
450        if (chopAfter != null) {
451          for (DN chopAfterDN : chopAfter.keySet()) {
452            if (!dn.equals(chopAfterDN) && dn.isDescendantOf(chopAfterDN)) {
453              return false;
454            }
455          }
456        }
457    
458        // Everything seemed to match.
459        return true;
460      }
461    
462      /**
463       * Get the absolute base DN of the subtree specification.
464       *
465       * @return Returns the absolute base DN of the subtree specification.
466       */
467      protected final DN getBaseDN() {
468        return baseDN;
469      }
470    
471      /**
472       * Determine if the common components of this subtree specification
473       * are equal to the common components of another subtre specification.
474       *
475       * @param other
476       *          The other subtree specification.
477       * @return Returns <code>true</code> if they are equal.
478       */
479      protected final boolean commonComponentsEquals(
480          SimpleSubtreeSpecification other) {
481    
482        if (this == other) {
483          return true;
484        }
485    
486        if (minimumDepth != other.minimumDepth) {
487          return false;
488        }
489    
490        if (maximumDepth != other.maximumDepth) {
491          return false;
492        }
493    
494        if (chopBefore != null && other.chopBefore != null) {
495          if (!chopBefore.values().equals(other.chopBefore.values())) {
496            return false;
497          }
498        } else if (chopBefore != other.chopBefore) {
499          return false;
500        }
501    
502        if (chopAfter != null && other.chopAfter != null) {
503          if (!chopAfter.values().equals(other.chopAfter.values())) {
504            return false;
505          }
506        } else if (chopAfter != other.chopAfter) {
507          return false;
508        }
509    
510        return true;
511      }
512    
513      /**
514       * Get a hash code of the subtree specification's common components.
515       *
516       * @return The computed hash code.
517       */
518      protected final int commonComponentsHashCode() {
519    
520        int hash = minimumDepth * 31 + maximumDepth;
521    
522        if (chopBefore != null) {
523          hash = hash * 31 + chopBefore.values().hashCode();
524        }
525    
526        if (chopAfter != null) {
527          hash = hash * 31 + chopAfter.values().hashCode();
528        }
529    
530        return hash;
531      }
532    
533      /**
534       * Get the set of chop after relative DNs.
535       *
536       * @return Returns the set of chop after relative DNs, or
537       *         <code>null</code> if there are not any.
538       */
539      public final Iterable<DN> getChopAfter() {
540    
541        if (chopAfter != null) {
542          return chopAfter.values();
543        } else {
544          return null;
545        }
546      }
547    
548      /**
549       * Get the set of chop before relative DNs.
550       *
551       * @return Returns the set of chop before relative DNs, or
552       *         <code>null</code> if there are not any.
553       */
554      public final Iterable<DN> getChopBefore() {
555    
556        if (chopBefore != null) {
557          return chopBefore.values();
558        } else {
559          return null;
560        }
561      }
562    
563      /**
564       * Get the maximum depth of the subtree specification.
565       *
566       * @return Returns the maximum depth (<0 indicates unlimited depth).
567       */
568      public final int getMaximumDepth() {
569        return maximumDepth;
570      }
571    
572      /**
573       * Get the minimum depth of the subtree specification.
574       *
575       * @return Returns the minimum depth (<=0 indicates unlimited depth).
576       */
577      public final int getMinimumDepth() {
578        return minimumDepth;
579      }
580    }