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