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.HashSet; 033 import java.util.InputMismatchException; 034 import java.util.NoSuchElementException; 035 036 import org.opends.server.types.DirectoryException; 037 import org.opends.server.types.DN; 038 import org.opends.server.types.Entry; 039 import org.opends.server.types.ResultCode; 040 import org.opends.server.types.SearchFilter; 041 import org.opends.server.util.StaticUtils; 042 043 /** 044 * A relative subtree specification. 045 * <p> 046 * Relative subtree specifications are very similar toRFC 3672 subtree 047 * specifications with the only difference being that the specification 048 * filter is not a set of refinements, but an LDAP search filter. 049 * <p> 050 * The string representation of a relative subtree specification is 051 * defined by the following grammar: 052 * 053 * <pre> 054 * SubtreeSpecification = "{" 055 * [ sp ss-relative-base ] 056 * [ sep sp ss-specificExclusions ] 057 * [ sep sp ss-minimum ] 058 * [ sep sp ss-maximum ] 059 * [ sep sp ss-specificationFilter ] 060 * sp "}" 061 * 062 * ss-relative-base = "relativeBase&quot msp DistinguishedName 063 * 064 * ss-specificExclusions = "specificExclusions&quot 065 * msp SpecificExclusions 066 * 067 * ss-minimum = "minimum&quot msp BaseDistance 068 * 069 * ss-maximum = "maximum&quot msp BaseDistance 070 * 071 * ss-specificationFilter = "specificationFilter&quot msp Filter 072 * 073 * SpecificExclusions = "{" 074 * [ sp SpecificExclusion 075 * ( "," sp SpecificExclusion ) ] 076 * sp "}" 077 * 078 * SpecificExclusion = chopBefore / chopAfter 079 * 080 * chopBefore = "chopBefore&quot ":" LocalName 081 * 082 * chopAfter = "chopAfter&quot ":" LocalName 083 * 084 * Filter = dquote *SafeUTF8Character dquote 085 * </pre> 086 */ 087 public final class RelativeSubtreeSpecification extends 088 SimpleSubtreeSpecification { 089 090 // The root DN. 091 private DN rootDN; 092 093 // The optional relative base DN. 094 private DN relativeBaseDN; 095 096 // The optional search filter. 097 private SearchFilter filter; 098 099 /** 100 * Parses the string argument as a relative subtree specification. 101 * 102 * @param rootDN 103 * The DN of the subtree specification's base entry. 104 * @param s 105 * The string to be parsed. 106 * @return The relative subtree specification represented by the 107 * string argument. 108 * @throws DirectoryException 109 * If the string does not contain a parsable relative 110 * subtree specification. 111 */ 112 public static RelativeSubtreeSpecification valueOf(DN rootDN, String s) 113 throws DirectoryException { 114 115 // Default values. 116 DN relativeBaseDN = null; 117 118 int minimum = -1; 119 int maximum = -1; 120 121 HashSet<DN> chopBefore = new HashSet<DN>(); 122 HashSet<DN> chopAfter = new HashSet<DN>(); 123 124 SearchFilter filter = null; 125 126 // Value must have an opening left brace. 127 Parser parser = new Parser(s); 128 boolean isValid = true; 129 130 try { 131 parser.skipLeftBrace(); 132 133 // Parse each element of the value sequence. 134 boolean isFirst = true; 135 136 while (true) { 137 if (parser.hasNextRightBrace()) { 138 // Make sure that there is a closing brace and no trailing 139 // text. 140 parser.skipRightBrace(); 141 142 if (parser.hasNext()) { 143 throw new java.util.InputMismatchException(); 144 } 145 break; 146 } 147 148 // Make sure that there is a comma separator if this is not the 149 // first element. 150 if (!isFirst) { 151 parser.skipSeparator(); 152 } else { 153 isFirst = false; 154 } 155 156 String key = parser.nextKey(); 157 if (key.equals("relativebase")) { 158 if (relativeBaseDN != null) { 159 // Relative base DN specified more than once. 160 throw new InputMismatchException(); 161 } 162 relativeBaseDN = DN.decode(parser.nextStringValue()); 163 } else if (key.equals("minimum")) { 164 if (minimum != -1) { 165 // Minimum specified more than once. 166 throw new InputMismatchException(); 167 } 168 minimum = parser.nextInt(); 169 } else if (key.equals("maximum")) { 170 if (maximum != -1) { 171 // Maximum specified more than once. 172 throw new InputMismatchException(); 173 } 174 maximum = parser.nextInt(); 175 } else if (key.equals("specificationfilter")) { 176 if (filter != null) { 177 // Filter specified more than once. 178 throw new InputMismatchException(); 179 } 180 filter = SearchFilter.createFilterFromString(parser 181 .nextStringValue()); 182 } else if (key.equals("specificexclusions")) { 183 if (!chopBefore.isEmpty() || !chopAfter.isEmpty()) { 184 // Specific exclusions specified more than once. 185 throw new InputMismatchException(); 186 } 187 188 parser.nextSpecificExclusions(chopBefore, chopAfter); 189 } else { 190 throw new InputMismatchException(); 191 } 192 } 193 194 // Make default minimum value is 0. 195 if (minimum < 0) { 196 minimum = 0; 197 } 198 199 // Check that the maximum, if specified, is gte the minimum. 200 if (maximum >= 0 && maximum < minimum) { 201 isValid = false; 202 } 203 } catch (InputMismatchException e) { 204 isValid = false; 205 } catch (NoSuchElementException e) { 206 isValid = false; 207 } 208 209 if (isValid) { 210 return new RelativeSubtreeSpecification(rootDN, relativeBaseDN, 211 minimum, maximum, chopBefore, chopAfter, filter); 212 } else { 213 Message message = 214 ERR_ATTR_SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_INVALID.get(s); 215 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 216 message); 217 } 218 } 219 220 /** 221 * Create a new relative subtree specification. 222 * 223 * @param rootDN 224 * The root DN of the subtree. 225 * @param relativeBaseDN 226 * The relative base DN (or <code>null</code> if not 227 * specified). 228 * @param minimumDepth 229 * The minimum depth (<=0 means unlimited). 230 * @param maximumDepth 231 * The maximum depth (<0 means unlimited). 232 * @param chopBefore 233 * The set of chop before local names (relative to the base 234 * DN), or <code>null</code> if there are none. 235 * @param chopAfter 236 * The set of chop after local names (relative to the base 237 * DN), or <code>null</code> if there are none. 238 * @param filter 239 * The optional search filter (<code>null</code> if there 240 * is no filter). 241 */ 242 public RelativeSubtreeSpecification(DN rootDN, DN relativeBaseDN, 243 int minimumDepth, int maximumDepth, Iterable<DN> chopBefore, 244 Iterable<DN> chopAfter, SearchFilter filter) { 245 super(relativeBaseDN == null ? rootDN : rootDN.concat(relativeBaseDN), 246 minimumDepth, maximumDepth, chopBefore, chopAfter); 247 248 249 this.rootDN = rootDN; 250 this.relativeBaseDN = relativeBaseDN; 251 this.filter = filter; 252 } 253 254 /** 255 * Get the root DN. 256 * 257 * @return Returns the root DN. 258 */ 259 public DN getRootDN() { 260 return rootDN; 261 } 262 263 /** 264 * Get the relative base DN. 265 * 266 * @return Returns the relative base DN or <code>null</code> if none 267 * was specified. 268 */ 269 public DN getRelativeBaseDN() { 270 return relativeBaseDN; 271 } 272 273 /** 274 * Get the specification filter. 275 * 276 * @return Returns the search filter, or <code>null</code> if there 277 * is no filter. 278 */ 279 public SearchFilter getFilter() { 280 return filter; 281 } 282 283 /** 284 * {@inheritDoc} 285 */ 286 @Override 287 public boolean isWithinScope(Entry entry) { 288 289 if (isDNWithinScope(entry.getDN())) { 290 try { 291 return filter.matchesEntry(entry); 292 } catch (DirectoryException e) { 293 // TODO: need to decide what to do with the exception here. It's 294 // probably safe to ignore, but we could log it perhaps. 295 return false; 296 } 297 } else { 298 return false; 299 } 300 } 301 302 /** 303 * {@inheritDoc} 304 */ 305 @Override 306 public StringBuilder toString(StringBuilder builder) { 307 308 boolean isFirstElement = true; 309 310 // Output the optional base DN. 311 builder.append("{"); 312 if (relativeBaseDN != null && !relativeBaseDN.isNullDN()) { 313 builder.append(" relativeBase "); 314 StaticUtils.toRFC3641StringValue(builder, relativeBaseDN.toString()); 315 isFirstElement = false; 316 } 317 318 // Output the optional specific exclusions. 319 Iterable<DN> chopBefore = getChopBefore(); 320 Iterable<DN> chopAfter = getChopAfter(); 321 322 if ((chopBefore != null && chopBefore.iterator().hasNext()) 323 || (chopAfter != null && chopAfter.iterator().hasNext())) { 324 325 if (!isFirstElement) { 326 builder.append(","); 327 } else { 328 isFirstElement = false; 329 } 330 builder.append(" specificExclusions { "); 331 332 boolean isFirst = true; 333 334 if (chopBefore != null) { 335 for (DN dn : chopBefore) { 336 if (!isFirst) { 337 builder.append(", chopBefore:"); 338 } else { 339 builder.append("chopBefore:"); 340 isFirst = false; 341 } 342 StaticUtils.toRFC3641StringValue(builder, dn.toString()); 343 } 344 } 345 346 if (chopAfter != null) { 347 for (DN dn : chopAfter) { 348 if (!isFirst) { 349 builder.append(", chopAfter:"); 350 } else { 351 builder.append("chopAfter:"); 352 isFirst = false; 353 } 354 StaticUtils.toRFC3641StringValue(builder, dn.toString()); 355 } 356 } 357 358 builder.append(" }"); 359 } 360 361 // Output the optional minimum depth. 362 if (getMinimumDepth() > 0) { 363 if (!isFirstElement) { 364 builder.append(","); 365 } else { 366 isFirstElement = false; 367 } 368 builder.append(" minimum "); 369 builder.append(getMinimumDepth()); 370 } 371 372 // Output the optional maximum depth. 373 if (getMaximumDepth() >= 0) { 374 if (!isFirstElement) { 375 builder.append(","); 376 } else { 377 isFirstElement = false; 378 } 379 builder.append(" maximum "); 380 builder.append(getMaximumDepth()); 381 } 382 383 // Output the optional filter. 384 if (filter != null) { 385 if (!isFirstElement) { 386 builder.append(","); 387 } else { 388 isFirstElement = false; 389 } 390 builder.append(" specificationFilter "); 391 StaticUtils.toRFC3641StringValue(builder, filter.toString()); 392 } 393 394 builder.append(" }"); 395 396 return builder; 397 } 398 399 /** 400 * {@inheritDoc} 401 */ 402 @Override 403 public boolean equals(Object obj) { 404 405 if (this == obj) { 406 return true; 407 } 408 409 if (obj instanceof RelativeSubtreeSpecification) { 410 RelativeSubtreeSpecification other = (RelativeSubtreeSpecification) obj; 411 412 if (!commonComponentsEquals(other)) { 413 return false; 414 } 415 416 if (!getBaseDN().equals(other.getBaseDN())) { 417 return false; 418 } 419 420 if (filter != null) { 421 return filter.equals(other.filter); 422 } else { 423 return filter == other.filter; 424 } 425 } 426 427 return false; 428 } 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override 434 public int hashCode() { 435 436 int hash = commonComponentsHashCode(); 437 438 hash = hash * 31 + getBaseDN().hashCode(); 439 440 if (filter != null) { 441 hash = hash * 31 + filter.hashCode(); 442 } 443 444 return hash; 445 } 446 }