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 }