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  = &quot;{&quot;       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 &quot;}&quot;
067     *
068     *  ss-absolute-base      = &quot;absoluteBase&amp;quot msp DistinguishedName
069     *
070     *  ss-specificExclusions = &quot;specificExclusions&amp;quot
071     *                                             msp SpecificExclusions
072     *
073     *  ss-minimum            = &quot;minimum&amp;quot msp BaseDistance
074     *
075     *  ss-maximum            = &quot;maximum&amp;quot msp BaseDistance
076     *
077     *  ss-specificationFilter = &quot;specificationFilter&amp;quot msp Filter
078     *
079     *  SpecificExclusions    = &quot;{&quot;
080     *                            [ sp SpecificExclusion
081     *                              ( &quot;,&quot; sp SpecificExclusion ) ]
082     *                       sp &quot;}&quot;
083     *
084     *  SpecificExclusion     = chopBefore / chopAfter
085     *
086     *  chopBefore            = &quot;chopBefore&amp;quot &quot;:&quot; LocalName
087     *
088     *  chopAfter             = &quot;chopAfter&amp;quot &quot;:&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    }