/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. */ package org.opends.server.core; import static org.opends.server.loggers.Debug.debugConstructor; import static org.opends.server.loggers.Debug.debugEnter; import java.util.InputMismatchException; import java.util.NoSuchElementException; import java.util.Scanner; import java.util.Set; import java.util.TreeMap; import java.util.regex.Pattern; import org.opends.server.api.SubtreeSpecification; import org.opends.server.types.DN; import org.opends.server.util.StaticUtils; /** * A simple subtree specification implementation that has a subtree * base, optional minimum and maximum depths, and a set of chop * specifications. */ public abstract class SimpleSubtreeSpecification extends SubtreeSpecification { // Fully qualified class name for debugging purposes. private static final String CLASS_NAME = SimpleSubtreeSpecification.class .getName(); // The absolute base of the subtree. private DN baseDN; // Optional minimum depth (<=0 means unlimited). private int minimumDepth; // Optional maximum depth (<0 means unlimited). private int maximumDepth; // Optional set of chop before absolute DNs (mapping to their // local-names). private TreeMap chopBefore; // Optional set of chop after absolute DNs (mapping to their // local-names). private TreeMap chopAfter; /** * Internal utility class which can be used by sub-classes to help * parse string representations. */ protected static final class Parser { // Text scanner used to parse the string value. private Scanner scanner; // Pattern used to detect left braces. private static Pattern LBRACE = Pattern.compile("\\{.*"); // Pattern used to parse left braces. private static Pattern LBRACE_TOKEN = Pattern.compile("\\{"); // Pattern used to detect right braces. private static Pattern RBRACE = Pattern.compile("\\}.*"); // Pattern used to parse right braces. private static Pattern RBRACE_TOKEN = Pattern.compile("\\}"); // Pattern used to detect comma separators. private static Pattern SEP = Pattern.compile(",.*"); // Pattern used to parse comma separators. private static Pattern SEP_TOKEN = Pattern.compile(","); // Pattern used to detect colon separators. private static Pattern COLON = Pattern.compile(":.*"); // Pattern used to parse colon separators. private static Pattern COLON_TOKEN = Pattern.compile(":"); // Pattern used to detect integer values. private static Pattern INT = Pattern.compile("\\d.*"); // Pattern used to parse integer values. private static Pattern INT_TOKEN = Pattern.compile("\\d+"); // Pattern used to detect name values. private static Pattern NAME = Pattern.compile("[\\w_;-].*"); // Pattern used to parse name values. private static Pattern NAME_TOKEN = Pattern.compile("[\\w_;-]+"); // Pattern used to detect RFC3641 string values. private static Pattern STRING_VALUE = Pattern.compile("\".*"); // Pattern used to parse RFC3641 string values. private static Pattern STRING_VALUE_TOKEN = Pattern .compile("\"([^\"]|(\"\"))*\""); /** * Create a new parser for a subtree specification string value. * * @param value * The subtree specification string value. */ public Parser(String value) { this.scanner = new Scanner(value); } /** * Skip a left-brace character. * * @throws InputMismatchException * If the next token is not a left-brace character. * @throws NoSuchElementException * If input is exhausted. */ public void skipLeftBrace() throws InputMismatchException, NoSuchElementException { nextValue(LBRACE, LBRACE_TOKEN); } /** * Skip a right-brace character. * * @throws InputMismatchException * If the next token is not a right-brace character. * @throws NoSuchElementException * If input is exhausted. */ public void skipRightBrace() throws InputMismatchException, NoSuchElementException { nextValue(RBRACE, RBRACE_TOKEN); } /** * Skip a comma separator. * * @throws InputMismatchException * If the next token is not a comma separator character. * @throws NoSuchElementException * If input is exhausted. */ public void skipSeparator() throws InputMismatchException, NoSuchElementException { nextValue(SEP, SEP_TOKEN); } /** * Skip a colon separator. * * @throws InputMismatchException * If the next token is not a colon separator character. * @throws NoSuchElementException * If input is exhausted. */ public void skipColon() throws InputMismatchException, NoSuchElementException { nextValue(COLON, COLON_TOKEN); } /** * Determine if the next token is a right-brace character. * * @return true if and only if the next token is a * valid right brace character. */ public boolean hasNextRightBrace() { return scanner.hasNext(RBRACE); } /** * Determine if there are remaining tokens. * * @return true if and only if there are remaining * tokens. */ public boolean hasNext() { return scanner.hasNext(); } /** * Scans the next token of the input as a key value. * * @return The lower-case key value scanned from the input. * @throws InputMismatchException * If the next token is not a valid key string. * @throws NoSuchElementException * If input is exhausted. */ public String nextKey() throws InputMismatchException, NoSuchElementException { return StaticUtils.toLowerCase(scanner.next()); } /** * Scans the next token of the input as a name value. *

* A name is any string containing only alpha-numeric characters or * hyphens, semi-colons, or underscores. * * @return The name value scanned from the input. * @throws InputMismatchException * If the next token is not a valid name string. * @throws NoSuchElementException * If input is exhausted. */ public String nextName() throws InputMismatchException, NoSuchElementException { return nextValue(NAME, NAME_TOKEN); } /** * Scans the next token of the input as a non-negative * int value. * * @return The name value scanned from the input. * @throws InputMismatchException * If the next token is not a valid non-negative integer * string. * @throws NoSuchElementException * If input is exhausted. */ public int nextInt() throws InputMismatchException, NoSuchElementException { String s = nextValue(INT, INT_TOKEN); return Integer.parseInt(s); } /** * Scans the next token of the input as a string quoted according to * the StringValue production in RFC 3641. *

* The return string has its outer double quotes removed and any * escaped inner double quotes unescaped. * * @return The string value scanned from the input. * @throws InputMismatchException * If the next token is not a valid string. * @throws NoSuchElementException * If input is exhausted. */ public String nextStringValue() throws InputMismatchException, NoSuchElementException { String s = nextValue(STRING_VALUE, STRING_VALUE_TOKEN); return s.substring(1, s.length() - 1).replace("\"\"", "\""); } /** * Scans the next tokens of the input as a set of specific * exclusions encoded according to the SpecificExclusion production * in RFC 3672. * * @param chopBefore * The set of chop before local names. * @param chopAfter * The set of chop after local names. * @throws InputMismatchException * If the common component did not have a valid syntax. * @throws NoSuchElementException * If input is exhausted. * @throws DirectoryException * If an error occurred when attempting to parse a DN * value. */ public void nextSpecificExclusions(Set chopBefore, Set chopAfter) throws InputMismatchException, NoSuchElementException, DirectoryException { // Skip leading open-brace. skipLeftBrace(); // Parse each chop DN in the sequence. boolean isFirstValue = true; while (true) { // Make sure that there is a closing brace. if (hasNextRightBrace()) { skipRightBrace(); break; } // Make sure that there is a comma separator if this is not // the first element. if (!isFirstValue) { skipSeparator(); } else { isFirstValue = false; } // Parse each chop specification which is of the form // :. String type = StaticUtils.toLowerCase(nextName()); skipColon(); if (type.equals("chopbefore")) { chopBefore.add(DN.decode(nextStringValue())); } else if (type.equals("chopafter")) { chopAfter.add(DN.decode(nextStringValue())); } else { throw new java.util.InputMismatchException(); } } } /** * Parse the next token from the string using the specified * patterns. * * @param head * The pattern used to determine if the next token is a * possible match. * @param content * The pattern used to parse the token content. * @return The next token matching the content * pattern. * @throws InputMismatchException * If the next token does not match the * content pattern. * @throws NoSuchElementException * If input is exhausted. */ private String nextValue(Pattern head, Pattern content) throws InputMismatchException, NoSuchElementException { if (!scanner.hasNext()) { throw new java.util.NoSuchElementException(); } if (!scanner.hasNext(head)) { throw new java.util.InputMismatchException(); } String s = scanner.findInLine(content); if (s == null) { throw new java.util.InputMismatchException(); } return s; } } /** * Create a new simple subtree specification. * * @param baseDN * The absolute base DN of the subtree. * @param minimumDepth * The minimum depth (<=0 means unlimited). * @param maximumDepth * The maximum depth (<0 means unlimited). * @param chopBefore * The set of chop before local names (relative to the base * DN), or null if there are none. * @param chopAfter * The set of chop after local names (relative to the base * DN), or null if there are none. */ protected SimpleSubtreeSpecification(DN baseDN, int minimumDepth, int maximumDepth, Iterable chopBefore, Iterable chopAfter) { assert debugConstructor(CLASS_NAME); this.baseDN = baseDN; this.minimumDepth = minimumDepth; this.maximumDepth = maximumDepth; if (chopBefore != null && chopBefore.iterator().hasNext()) { // Calculate the absolute DNs. this.chopBefore = new TreeMap(); for (DN localName : chopBefore) { this.chopBefore.put(new DN(baseDN, localName), localName); } } else { // No chop before specifications. this.chopBefore = null; } if (chopAfter != null && chopAfter.iterator().hasNext()) { // Calculate the absolute DNs. this.chopAfter = new TreeMap(); for (DN localName : chopAfter) { this.chopAfter.put(new DN(baseDN, localName), localName); } } else { // No chop after specifications. this.chopAfter = null; } } /** * Determine if the specified DN is within the scope of the subtree * specification. * * @param dn * The distringuished name. * @return Returns true if the DN is within the scope * of the subtree specification, or false * otherwise. */ protected final boolean isDNWithinScope(DN dn) { assert debugEnter(CLASS_NAME, "isDNWithinScope"); if (!dn.isDescendantOf(baseDN)) { return false; } // Check minimum and maximum depths. int baseRDNCount = baseDN.getRDNComponents().length; if (minimumDepth > 0) { int entryRDNCount = dn.getRDNComponents().length; if (entryRDNCount - baseRDNCount < minimumDepth) { return false; } } if (maximumDepth >= 0) { int entryRDNCount = dn.getRDNComponents().length; if (entryRDNCount - baseRDNCount > maximumDepth) { return false; } } // Check exclusions. if (chopBefore != null) { for (DN chopBeforeDN : chopBefore.keySet()) { if (dn.isDescendantOf(chopBeforeDN)) { return false; } } } if (chopAfter != null) { for (DN chopAfterDN : chopAfter.keySet()) { if (!dn.equals(chopAfterDN) && dn.isDescendantOf(chopAfterDN)) { return false; } } } // Everything seemed to match. return true; } /** * Get the absolute base DN of the subtree specification. * * @return Returns the absolute base DN of the subtree specification. */ protected final DN getBaseDN() { assert debugEnter(CLASS_NAME, "getBaseDN"); return baseDN; } /** * Determine if the common components of this subtree specification * are equal to the common components of another subtre specification. * * @param other * The other subtree specification. * @return Returns true if they are equal. */ protected final boolean commonComponentsEquals( SimpleSubtreeSpecification other) { assert debugEnter(CLASS_NAME, "commonComponentsEquals"); if (this == other) { return true; } if (minimumDepth != other.minimumDepth) { return false; } if (maximumDepth != other.maximumDepth) { return false; } if (chopBefore != null && other.chopBefore != null) { if (!chopBefore.values().equals(other.chopBefore.values())) { return false; } } else if (chopBefore != other.chopBefore) { return false; } if (chopAfter != null && other.chopAfter != null) { if (!chopAfter.values().equals(other.chopAfter.values())) { return false; } } else if (chopAfter != other.chopAfter) { return false; } return true; } /** * Get a hash code of the subtree specification's common components. * * @return The computed hash code. */ protected final int commonComponentsHashCode() { assert debugEnter(CLASS_NAME, "commonComponentsHashCode"); int hash = minimumDepth * 31 + maximumDepth; if (chopBefore != null) { hash = hash * 31 + chopBefore.values().hashCode(); } if (chopAfter != null) { hash = hash * 31 + chopAfter.values().hashCode(); } return hash; } /** * Get the set of chop after relative DNs. * * @return Returns the set of chop after relative DNs, or * null if there are not any. */ public final Iterable getChopAfter() { assert debugEnter(CLASS_NAME, "getChopAfter"); if (chopAfter != null) { return chopAfter.values(); } else { return null; } } /** * Get the set of chop before relative DNs. * * @return Returns the set of chop before relative DNs, or * null if there are not any. */ public final Iterable getChopBefore() { assert debugEnter(CLASS_NAME, "getChopBefore"); if (chopBefore != null) { return chopBefore.values(); } else { return null; } } /** * Get the maximum depth of the subtree specification. * * @return Returns the maximum depth (<0 indicates unlimited depth). */ public final int getMaximumDepth() { assert debugEnter(CLASS_NAME, "getMaximumDepth"); return maximumDepth; } /** * Get the minimum depth of the subtree specification. * * @return Returns the minimum depth (<=0 indicates unlimited depth). */ public final int getMinimumDepth() { assert debugEnter(CLASS_NAME, "getMinimumDepth"); return minimumDepth; } }