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  = &quot;{&quot;
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 &quot;}&quot;
061     *
062     *  ss-relative-base      = &quot;relativeBase&amp;quot msp DistinguishedName
063     *
064     *  ss-specificExclusions = &quot;specificExclusions&amp;quot
065     *                                             msp SpecificExclusions
066     *
067     *  ss-minimum            = &quot;minimum&amp;quot msp BaseDistance
068     *
069     *  ss-maximum            = &quot;maximum&amp;quot msp BaseDistance
070     *
071     *  ss-specificationFilter = &quot;specificationFilter&amp;quot msp Filter
072     *
073     *  SpecificExclusions    = &quot;{&quot;
074     *                              [ sp SpecificExclusion
075     *                                  ( &quot;,&quot; sp SpecificExclusion ) ]
076     *                       sp &quot;}&quot;
077     *
078     *  SpecificExclusion     = chopBefore / chopAfter
079     *
080     *  chopBefore            = &quot;chopBefore&amp;quot &quot;:&quot; LocalName
081     *
082     *  chopAfter             = &quot;chopAfter&amp;quot &quot;:&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    }