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 }