View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.ldap.client.api.search;
21  
22  import org.apache.directory.api.i18n.I18n;
23  
24  /**
25   * A builder for constructing well formed search filters according to
26   * <a href="https://tools.ietf.org/html/rfc4515.html">RFC 4515</a>.  This 
27   * builder is most convenient when you use static imports.  For example:
28   * <pre>
29   * import static org.apache.directory.ldap.client.api.search.FilterBuilder.and;
30   * import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
31   * import static org.apache.directory.ldap.client.api.search.FilterBuilder.or;
32   * 
33   * ...
34   * 
35   *         String filter = 
36   *                 or(
37   *                     and( 
38   *                         equal( "givenName", "kermit" ), 
39   *                         equal( "sn", "the frog" ) ),
40   *                     and( 
41   *                         equal( "givenName", "miss" ), 
42   *                         equal( "sn", "piggy" ) ) )
43   *                 .toString()
44   * </pre>
45   *
46   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
47   */
48  public class FilterBuilder
49  {
50      /** The built filter */
51      /* No qualifier */ Filter filter;
52  
53  
54      /**
55       * A private constructor that creates a new instance of a FilterBuilder
56       * containing a given filter.
57       * 
58       * @param filter The filter
59       */
60      /* No qualifier*/ FilterBuilder( Filter filter )
61      {
62          this.filter = filter;
63      }
64  
65  
66      /**
67       * Returns a new FilterBuilder that will <code>&amp;</code> together all of the 
68       * supplied filters.  For example:
69       * 
70       * <pre>
71       * and( equal( "givenName", "kermit" ), equal( "sn", "the frog" ) ).toString()
72       * </pre>
73       * would result in the string:
74       * <pre>
75       * (&amp;(givenName=kermit)(sn=the frog))
76       * </pre>
77       * 
78       * Which would match all entries with a given name of <code>kermit</code>
79       * and a surname <code>the frog</code>.
80       *
81       * @param filters The filters to and together
82       * @return A new FilterBuilder
83       */
84      public static FilterBuilder and( FilterBuilder... filters )
85      {
86          SetOfFiltersFilter filter = SetOfFiltersFilter.and();
87  
88          for ( FilterBuilder builder : filters )
89          {
90              filter.add( builder.filter );
91          }
92  
93          return new FilterBuilder( filter );
94      }
95  
96  
97      /**
98       * Returns a new FilterBuilder for testing the approximate equality of an 
99       * attribute. For example:
100      * 
101      * <pre>
102      * approximatelyEqual( "l", "san fransico" ).toString();
103      * </pre>
104      * would result in the string:
105      * <pre>
106      * (l~=san fransico)
107      * </pre>
108      * 
109      * Which <i>MIGHT</i> match results whose locality is 
110      * <code>San Francisco</code>.  The matching rule used to apply this filter
111      * is dependent on the server implementation.
112      *
113      * @param attribute The attribute 
114      * @param value The value
115      * @return A new FilterBuilder
116      */
117     public static FilterBuilder approximatelyEqual( String attribute, String value )
118     {
119         return new FilterBuilder( AttributeValueAssertionFilter.approximatelyEqual( attribute, value ) );
120     }
121 
122 
123     /**
124      * Returns a new FilterBuilder for testing equality of an attribute. For 
125      * example:
126      * 
127      * <pre>
128      * equal( "cn", "Kermit The Frog" ).toString();
129      * </pre>
130      * would result in the string:
131      * <pre>
132      * (cn&gt;=Kermit The Frog)
133      * </pre>
134      * 
135      * Which would match entries with the common name 
136      * <code>Kermit The Frog</code>.
137      *
138      * @param attribute The attribute 
139      * @param value The value
140      * @return A new FilterBuilder
141      */
142     public static FilterBuilder equal( String attribute, String value )
143     {
144         return new FilterBuilder( AttributeValueAssertionFilter.equal( attribute, value ) );
145     }
146 
147 
148     /**
149      * Creates an extensible match filter by calling 
150      * {@link #extensible(String, String) extensible(null, value)}.
151      *
152      * @param value The value to test for
153      * @return A new MatchingRuleAssertionFilterBuilder
154      */
155     public static MatchingRuleAssertionFilterBuilder extensible( String value )
156     {
157         return new MatchingRuleAssertionFilterBuilder( null, value );
158     }
159 
160 
161     /**
162      * Creates an extensible match filter.  This filter can be used to specify
163      * that dn attributes should be included in the match, which matcher to 
164      * use, or that all attributes that support a specific matcher will be
165      * checked.  For example:
166      * 
167      * <pre>
168      * extensible( "sn", "Barney Rubble" )
169      *     .useDnAttributes()
170      *     .setMatchingRule( "2.4.6.8.10" )
171      *     .toString();
172      * </pre>
173      * would result in the string:
174      * <pre>
175      * (sn:dn:2.4.6.8.10:=Barney Rubble)
176      * </pre>
177      * 
178      * Not that the specialized filter builder that is returned <b>IS</b> a 
179      * FilterBuilder so it can be chained with other filters.  For example:
180      * 
181      * <pre>
182      * and(
183      *     extensible( "sn", "Rubble" )
184      *         .useDnAttributes()
185      *         .setMatchingRule( "2.4.6.8.10" ),
186      *     equal( "givenName", "Barney" ) )
187      *     .toString();
188      * </pre>
189      *
190      * @param attribute The attribute to test
191      * @param value The value to test for
192      * @return A new MatchingRuleAssertionFilterBuilder
193      */
194     public static MatchingRuleAssertionFilterBuilder extensible( String attribute, String value )
195     {
196         return new MatchingRuleAssertionFilterBuilder( attribute, value );
197     }
198     
199     
200     /**
201      * Returns a new FilterBuilder for testing lexicographical greater than.  
202      * For example:
203      * 
204      * <pre>
205      * greaterThanOrEqual( "sn", "n" ).toString();
206      * </pre>
207      * would result in the string:
208      * <pre>
209      * (sn&gt;=n)
210      * </pre>
211      * 
212      * which would match results whose surname starts with the second half of
213      * the alphabet.  
214      *
215      * @param attribute The attribute 
216      * @param value The value
217      * @return A new FilterBuilder
218      */
219     public static FilterBuilder greaterThanOrEqual( String attribute, String value )
220     {
221         return new FilterBuilder( AttributeValueAssertionFilter.greaterThanOrEqual( attribute, value ) );
222     }
223 
224 
225     /**
226      * Returns a new FilterBuilder for testing lexicographical less than.  For
227      * example:
228      * 
229      * <pre>
230      * lessThanOrEqual( "sn", "mzzzzzz" ).toString();
231      * </pre>
232      * would result in the string:
233      * <pre>
234      * (sn&lt;=mzzzzzz)
235      * </pre>
236      * 
237      * which would match results whose surname starts with the first half of
238      * the alphabet.  <i>Note, this is not perfect, but if you know anybody with
239      * a last name that starts with an <code>m</code> followed by six
240      * <code>z</code>'s...</i>
241      *
242      * @param attribute The attribute 
243      * @param value The value
244      * @return A new FilterBuilder
245      */
246     public static FilterBuilder lessThanOrEqual( String attribute, String value )
247     {
248         return new FilterBuilder( AttributeValueAssertionFilter.lessThanOrEqual( attribute, value ) );
249     }
250 
251 
252     /**
253      * Returns a new FilterBuilder for negating another filter.  For example:
254      * 
255      * <pre>
256      * not( present( "givenName" ) ).toString();
257      * </pre>
258      * would result in the string:
259      * <pre>
260      * (!(givenName=*))
261      * </pre>
262      *
263      * @param builder The filter to negate
264      * @return A new FilterBuilder
265      */
266     public static FilterBuilder not( FilterBuilder builder )
267     {
268         return new FilterBuilder( UnaryFilter.not( builder.filter ) );
269     }
270 
271 
272     /**
273      * Returns a new FilterBuilder that will <code>|</code> together all of the 
274      * supplied filters.  For example:
275      * 
276      * <pre>
277      * or( equal( "givenName", "kermit" ), equal( "givenName", "walter" ) ).toString()
278      * </pre>
279      * would result in the string:
280      * <pre>
281      * (|(givenName=kermit)(givenName=walter))
282      * </pre>
283      * 
284      * Which would match any entry with the <code>givenName</code> of either
285      * <code>kermit</code> or <code>walter</code>.
286      *
287      * @param builders The filters to or together
288      * @return A new FilterBuilder
289      */
290     public static FilterBuilder or( FilterBuilder... builders )
291     {
292         SetOfFiltersFilter filter = SetOfFiltersFilter.or();
293 
294         for ( FilterBuilder builder : builders )
295         {
296             filter.add( builder.filter );
297         }
298 
299         return new FilterBuilder( filter );
300     }
301 
302 
303     /**
304      * Returns a new FilterBuilder for testing the presence of an attributes.  
305      * For example:
306      * 
307      * <pre>
308      * present( "givenName" ).toString();
309      * </pre>
310      * would result in the string:
311      * <pre>
312      * (givenName=*)
313      * </pre>
314      * 
315      * Which would match any entry that has a <code>givenName</code> attribute.
316      *
317      * @param attribute The attribute to test the presence of
318      * @return A new FilterBuilder
319      */
320     public static FilterBuilder present( String attribute )
321     {
322         return new FilterBuilder( AttributeDescriptionFilter.present( attribute ) );
323     }
324 
325 
326     /**
327      * Returns a new FilterBuilder that will construct a SubString filter, with an <em>initial</em> part, 
328      * and zero to N <em>any</em> part, but no <em>final</em> part.  
329      * 
330      * For instance:
331      * 
332      * <pre>
333      * startswith( "sn", "Th", "Soft", "Foun" )).toString()
334      * </pre>
335      * would result in the string:
336      * <pre>
337      * (sn=Th*Soft*Foun*)
338      * </pre>
339      * 
340      * Which would match any entry with the <code>sn</code> starting with <code>'Th'</code>, and 
341      * having a <code>Soft</code> and <code>Foun</code> strings in the middle, like 
342      * 'The Apache Software Foundation'.
343      *
344      * @param attribute The attribute to use in the filter
345      * @param parts The sub elements to use in the filter
346      * @return A new FilterBuilder
347      */
348     public static FilterBuilder startsWith( String attribute, String... parts )
349     {
350         if ( ( parts == null ) || ( parts.length == 0 ) )
351         {
352             throw new IllegalArgumentException( I18n.err( I18n.ERR_04162_INITIAL_PART_NEEDED ) );
353         }
354 
355         return new FilterBuilder( SubstringFilter.startsWith( attribute, parts ) );
356     }
357 
358 
359     /**
360      * Returns a new FilterBuilder that will construct a SubString filter, with an <em>initial</em> part, 
361      * and zero to N <em>any</em> parts, but no <em>final</em> part.  
362      * 
363      * For instance:
364      * 
365      * <pre>
366      * startswith( "sn", "Th", "Soft", "Foun" ).toString()
367      * </pre>
368      * would result in the string:
369      * <pre>
370      * (sn=Th*Soft*Foun*)
371      * </pre>
372      * 
373      * Which would match any entry with the <code>sn</code> starting with <code>'Th'</code>, and 
374      * having a <code>Soft</code> and <code>Foun</code> strings in the middle, like 
375      * 'The Apache Software Foundation'.
376      *
377      * @param attribute The attribute to use in the filter
378      * @param parts The sub elements to use in the filter
379      * @return A new FilterBuilder
380      */
381     public static FilterBuilder endsWith( String attribute, String... parts )
382     {
383         if ( ( parts == null ) || ( parts.length == 0 ) )
384         {
385             throw new IllegalArgumentException( I18n.err( I18n.ERR_04163_FINAL_PART_NEEDED ) );
386         }
387 
388         return new FilterBuilder( SubstringFilter.endsWith( attribute, parts ) );
389     }
390 
391 
392     /**
393      * Returns a new FilterBuilder that will construct a SubString filter, with zero to N <em>any</em> parts, 
394      * but no <em>initial</em> or <em>final</em> parts.  
395      * 
396      * For instance:
397      * 
398      * <pre>
399      * contains( "sn", "Soft", "Foun" )).toString()
400      * </pre>
401      * would result in the string:
402      * <pre>
403      * (sn=*Soft*Foun*)
404      * </pre>
405      * 
406      * Which would match any entry with the <code>sn</code> having a <code>Soft</code> 
407      * and <code>Foun</code> strings in the middle, like 
408      * 'The Apache Software Foundation'.
409      *
410      * @param attribute The attribute to use in the filter
411      * @param parts The sub elements to use in the filter
412      * @return A new FilterBuilder
413      */
414     public static FilterBuilder contains( String attribute, String... parts )
415     {
416         if ( ( parts == null ) || ( parts.length == 0 ) )
417         {
418             throw new IllegalArgumentException( I18n.err( I18n.ERR_04164_ANY_PART_NEEDED ) );
419         }
420 
421         return new FilterBuilder( SubstringFilter.contains( attribute, parts ) );
422     }
423 
424 
425     /**
426      * Returns a new FilterBuilder that will construct a SubString filter, with a <em>initial</em> part, 
427      * zero to N <em>any</em> parts, and a <em>final</em> part.
428      * 
429      * For instance:
430      * 
431      * <pre>
432      * substring( "sn", "The", "Soft", "Foun", "ion" )).toString()
433      * </pre>
434      * would result in the string:
435      * <pre>
436      * (sn=The*Soft*Foun*ion)
437      * </pre>
438      * 
439      * Which would match any entry with the <code>sn</code> having a <code>Soft</code> 
440      * and <code>Foun</code> strings in the middle, starts with <code>The</code> and ends with <code>ion</code> like 
441      * 'The Apache Software Foundation'.
442      * <p>
443      * Note that if we have only two strings in the parts, they will be the <em>initial</em> and <em>final</em> ones :
444      * 
445      * <pre>
446      * substring( "sn", "The", "ion" )).toString()
447      * </pre>
448      * would result in the string:
449      * <pre>
450      * (sn=The*ion)
451      * </pre>
452      * 
453      * @param attribute The attribute to use in the filter
454      * @param parts The sub elements to use in the filter
455      * @return A new FilterBuilder
456      */
457     public static FilterBuilder substring( String attribute, String... parts )
458     {
459         if ( ( parts == null ) || ( parts.length == 0 ) )
460         {
461             throw new IllegalArgumentException( I18n.err( I18n.ERR_04165_INITIAL_ANY_FINAL_PART_NEEDED ) );
462         }
463 
464         return new FilterBuilder( SubstringFilter.substring( attribute, parts ) );
465     }
466 
467 
468     /**
469      * Returns the string version of the filter represented by this FilterBuilder.
470      * 
471      * @return The string representation of the filter
472      */
473     @Override
474     public String toString()
475     {
476         return filter.build().toString();
477     }
478 }