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 }