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 }