001/*
002 *   Licensed to the Apache Software Foundation (ASF) under one
003 *   or more contributor license agreements.  See the NOTICE file
004 *   distributed with this work for additional information
005 *   regarding copyright ownership.  The ASF licenses this file
006 *   to you under the Apache License, Version 2.0 (the
007 *   "License"); you may not use this file except in compliance
008 *   with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *   Unless required by applicable law or agreed to in writing,
013 *   software distributed under the License is distributed on an
014 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *   KIND, either express or implied.  See the License for the
016 *   specific language governing permissions and limitations
017 *   under the License.
018 *
019 */
020package org.apache.directory.ldap.client.api.search;
021
022
023/**
024 * A builder for constructing well formed search filters according to
025 * <a href="https://tools.ietf.org/html/rfc4515.html">RFC 4515</a>.  This 
026 * builder is most convenient when you use static imports.  For example:
027 * <pre>
028 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.and;
029 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
030 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.or;
031 * 
032 * ...
033 * 
034 *         String filter = 
035 *                 or(
036 *                     and( 
037 *                         equal( "givenName", "kermit" ), 
038 *                         equal( "sn", "the frog" ) ),
039 *                     and( 
040 *                         equal( "givenName", "miss" ), 
041 *                         equal( "sn", "piggy" ) ) )
042 *                 .toString()
043 * </pre>
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class FilterBuilder
048{
049    /** The built filter */
050    /* No qualifier */ Filter filter;
051
052
053    /**
054     * A private constructor that creates a new instance of a FilterBuilder
055     * containing a given filter.
056     */
057    /* No qualifier*/ FilterBuilder( Filter filter )
058    {
059        this.filter = filter;
060    }
061
062
063    /**
064     * Returns a new FilterBuilder that will <code>&amp;</code> together all of the 
065     * supplied filters.  For example:
066     * 
067     * <pre>
068     * and( equal( "givenName", "kermit" ), equal( "sn", "the frog" ) ).toString()
069     * </pre>
070     * would result in the string:
071     * <pre>
072     * (&amp;(givenName=kermit)(sn=the frog))
073     * </pre>
074     * 
075     * Which would match all entries with a given name of <code>kermit</code>
076     * and a surname <code>the frog</code>.
077     *
078     * @param filters The filters to and together
079     * @return A new FilterBuilder
080     */
081    public static FilterBuilder and( FilterBuilder... filters )
082    {
083        SetOfFiltersFilter filter = SetOfFiltersFilter.and();
084
085        for ( FilterBuilder builder : filters )
086        {
087            filter.add( builder.filter );
088        }
089
090        return new FilterBuilder( filter );
091    }
092
093
094    /**
095     * Returns a new FilterBuilder for testing the approximate equality of an 
096     * attribute. For example:
097     * 
098     * <pre>
099     * approximatelyEqual( "l", "san fransico" ).toString();
100     * </pre>
101     * would result in the string:
102     * <pre>
103     * (l~=san fransico)
104     * </pre>
105     * 
106     * Which <i>MIGHT</i> match results whose locality is 
107     * <code>San Francisco</code>.  The matching rule used to apply this filter
108     * is dependent on the server implementation.
109     *
110     * @param attribute The attribute 
111     * @param value The value
112     * @return A new FilterBuilder
113     */
114    public static FilterBuilder approximatelyEqual( String attribute, String value )
115    {
116        return new FilterBuilder( AttributeValueAssertionFilter.approximatelyEqual( attribute, value ) );
117    }
118
119
120    /**
121     * Returns a new FilterBuilder for testing equality of an attribute. For 
122     * example:
123     * 
124     * <pre>
125     * equal( "cn", "Kermit The Frog" ).toString();
126     * </pre>
127     * would result in the string:
128     * <pre>
129     * (cn&gt;=Kermit The Frog)
130     * </pre>
131     * 
132     * Which would match entries with the common name 
133     * <code>Kermit The Frog</code>.
134     *
135     * @param attribute The attribute 
136     * @param value The value
137     * @return A new FilterBuilder
138     */
139    public static FilterBuilder equal( String attribute, String value )
140    {
141        return new FilterBuilder( AttributeValueAssertionFilter.equal( attribute, value ) );
142    }
143
144
145    /**
146     * Creates an extensible match filter by calling 
147     * {@link #extensible(String, String) extensible(null, value)}.
148     *
149     * @param value The value to test for
150     * @return A new MatchingRuleAssertionFilterBuilder
151     */
152    public static MatchingRuleAssertionFilterBuilder extensible( String value )
153    {
154        return new MatchingRuleAssertionFilterBuilder( null, value );
155    }
156
157
158    /**
159     * Creates an extensible match filter.  This filter can be used to specify
160     * that dn attributes should be included in the match, which matcher to 
161     * use, or that all attributes that support a specific matcher will be
162     * checked.  For example:
163     * 
164     * <pre>
165     * extensible( "sn", "Barney Rubble" )
166     *     .useDnAttributes()
167     *     .setMatchingRule( "2.4.6.8.10" )
168     *     .toString();
169     * </pre>
170     * would result in the string:
171     * <pre>
172     * (sn:dn:2.4.6.8.10:=Barney Rubble)
173     * </pre>
174     * 
175     * Not that the specialized filter builder that is returned <b>IS</b> a 
176     * FilterBuilder so it can be chained with other filters.  For example:
177     * 
178     * <pre>
179     * and(
180     *     extensible( "sn", "Rubble" )
181     *         .useDnAttributes()
182     *         .setMatchingRule( "2.4.6.8.10" ),
183     *     equal( "givenName", "Barney" ) )
184     *     .toString();
185     * </pre>
186     *
187     * @param attribute The attribute to test
188     * @param value The value to test for
189     * @return A new MatchingRuleAssertionFilterBuilder
190     */
191    public static MatchingRuleAssertionFilterBuilder extensible( String attribute, String value )
192    {
193        return new MatchingRuleAssertionFilterBuilder( attribute, value );
194    }
195    
196    
197    /**
198     * Returns a new FilterBuilder for testing lexicographical greater than.  
199     * For example:
200     * 
201     * <pre>
202     * greaterThanOrEqual( "sn", "n" ).toString();
203     * </pre>
204     * would result in the string:
205     * <pre>
206     * (sn&gt;=n)
207     * </pre>
208     * 
209     * which would match results whose surname starts with the second half of
210     * the alphabet.  
211     *
212     * @param attribute The attribute 
213     * @param value The value
214     * @return A new FilterBuilder
215     */
216    public static FilterBuilder greaterThanOrEqual( String attribute, String value )
217    {
218        return new FilterBuilder( AttributeValueAssertionFilter.greaterThanOrEqual( attribute, value ) );
219    }
220
221
222    /**
223     * Returns a new FilterBuilder for testing lexicographical less than.  For
224     * example:
225     * 
226     * <pre>
227     * lessThanOrEqual( "sn", "mzzzzzz" ).toString();
228     * </pre>
229     * would result in the string:
230     * <pre>
231     * (sn&lt;=mzzzzzz)
232     * </pre>
233     * 
234     * which would match results whose surname starts with the first half of
235     * the alphabet.  <i>Note, this is not perfect, but if you know anybody with
236     * a last name that starts with an <code>m</code> followed by six
237     * <code>z</code>'s...</i>
238     *
239     * @param attribute The attribute 
240     * @param value The value
241     * @return A new FilterBuilder
242     */
243    public static FilterBuilder lessThanOrEqual( String attribute, String value )
244    {
245        return new FilterBuilder( AttributeValueAssertionFilter.lessThanOrEqual( attribute, value ) );
246    }
247
248
249    /**
250     * Returns a new FilterBuilder for negating another filter.  For example:
251     * 
252     * <pre>
253     * not( present( "givenName" ) ).toString();
254     * </pre>
255     * would result in the string:
256     * <pre>
257     * (!(givenName=*))
258     * </pre>
259     *
260     * @param builder The filter to negate
261     * @return A new FilterBuilder
262     */
263    public static FilterBuilder not( FilterBuilder builder )
264    {
265        return new FilterBuilder( UnaryFilter.not( builder.filter ) );
266    }
267
268
269    /**
270     * Returns a new FilterBuilder that will <code>|</code> together all of the 
271     * supplied filters.  For example:
272     * 
273     * <pre>
274     * or( equal( "givenName", "kermit" ), equal( "givenName", "walter" ) ).toString()
275     * </pre>
276     * would result in the string:
277     * <pre>
278     * (|(givenName=kermit)(givenName=walter))
279     * </pre>
280     * 
281     * Which would match any entry with the <code>givenName</code> of either
282     * <code>kermit</code> or <code>walter</code>.
283     *
284     * @param builders The filters to or together
285     * @return A new FilterBuilder
286     */
287    public static FilterBuilder or( FilterBuilder... builders )
288    {
289        SetOfFiltersFilter filter = SetOfFiltersFilter.or();
290
291        for ( FilterBuilder builder : builders )
292        {
293            filter.add( builder.filter );
294        }
295
296        return new FilterBuilder( filter );
297    }
298
299
300    /**
301     * Returns a new FilterBuilder for testing the presence of an attributes.  
302     * For example:
303     * 
304     * <pre>
305     * present( "givenName" ).toString();
306     * </pre>
307     * would result in the string:
308     * <pre>
309     * (givenName=*)
310     * </pre>
311     * 
312     * Which would match any entry that has a <code>givenName</code> attribute.
313     *
314     * @param attribute The attribute to test the presence of
315     * @return A new FilterBuilder
316     */
317    public static FilterBuilder present( String attribute )
318    {
319        return new FilterBuilder( AttributeDescriptionFilter.present( attribute ) );
320    }
321
322
323    /**
324     * Returns a new FilterBuilder that will construct a SubString filter, with an <em>initial</em> part, 
325     * and zero to N <em>any</em> part, but no <em>final</em> part.  
326     * 
327     * For instance:
328     * 
329     * <pre>
330     * startswith( "sn", "Th", "Soft", "Foun" )).toString()
331     * </pre>
332     * would result in the string:
333     * <pre>
334     * (sn=Th*Soft*Foun*)
335     * </pre>
336     * 
337     * Which would match any entry with the <code>sn</code> starting with <code>'Th'</code>, and 
338     * having a <code>Soft</code> and <code>Foun</code> strings in the middle, like 
339     * 'The Apache Software Foundation'.
340     *
341     * @param attribute The attribute to use in the filter
342     * @param parts The sub elements to use in the filter
343     * @return A new FilterBuilder
344     */
345    public static FilterBuilder startsWith( String attribute, String... parts )
346    {
347        if ( ( parts == null ) || ( parts.length == 0 ) )
348        {
349            throw new IllegalArgumentException( "An 'initial' part is needed" );
350        }
351
352        return new FilterBuilder( SubstringFilter.startsWith( attribute, parts ) );
353    }
354
355
356    /**
357     * Returns a new FilterBuilder that will construct a SubString filter, with an <em>initial</em> part, 
358     * and zero to N <em>any</em> parts, but no <em>final</em> part.  
359     * 
360     * For instance:
361     * 
362     * <pre>
363     * startswith( "sn", "Th", "Soft", "Foun" ).toString()
364     * </pre>
365     * would result in the string:
366     * <pre>
367     * (sn=Th*Soft*Foun*)
368     * </pre>
369     * 
370     * Which would match any entry with the <code>sn</code> starting with <code>'Th'</code>, and 
371     * having a <code>Soft</code> and <code>Foun</code> strings in the middle, like 
372     * 'The Apache Software Foundation'.
373     *
374     * @param attribute The attribute to use in the filter
375     * @param parts The sub elements to use in the filter
376     * @return A new FilterBuilder
377     */
378    public static FilterBuilder endsWith( String attribute, String... parts )
379    {
380        if ( ( parts == null ) || ( parts.length == 0 ) )
381        {
382            throw new IllegalArgumentException( "At a 'final' part is needed" );
383        }
384
385        return new FilterBuilder( SubstringFilter.endsWith( attribute, parts ) );
386    }
387
388
389    /**
390     * Returns a new FilterBuilder that will construct a SubString filter, with zero to N <em>any</em> parts, 
391     * but no <em>initial</em> or <em>final</em> parts.  
392     * 
393     * For instance:
394     * 
395     * <pre>
396     * contains( "sn", "Soft", "Foun" )).toString()
397     * </pre>
398     * would result in the string:
399     * <pre>
400     * (sn=*Soft*Foun*)
401     * </pre>
402     * 
403     * Which would match any entry with the <code>sn</code> having a <code>Soft</code> 
404     * and <code>Foun</code> strings in the middle, like 
405     * 'The Apache Software Foundation'.
406     *
407     * @param attribute The attribute to use in the filter
408     * @param parts The sub elements to use in the filter
409     * @return A new FilterBuilder
410     */
411    public static FilterBuilder contains( String attribute, String... parts )
412    {
413        if ( ( parts == null ) || ( parts.length == 0 ) )
414        {
415            throw new IllegalArgumentException( "At least one 'any' part is needed" );
416        }
417
418        return new FilterBuilder( SubstringFilter.contains( attribute, parts ) );
419    }
420
421
422    /**
423     * Returns a new FilterBuilder that will construct a SubString filter, with a <em>initial</em> part, 
424     * zero to N <em>any</em> parts, and a <em>final</em> part.
425     * 
426     * For instance:
427     * 
428     * <pre>
429     * substring( "sn", "The", "Soft", "Foun", "ion" )).toString()
430     * </pre>
431     * would result in the string:
432     * <pre>
433     * (sn=The*Soft*Foun*ion)
434     * </pre>
435     * 
436     * Which would match any entry with the <code>sn</code> having a <code>Soft</code> 
437     * and <code>Foun</code> strings in the middle, starts with <code>The</code> and ends with <code>ion</code> like 
438     * 'The Apache Software Foundation'.
439     * <p>
440     * Note that if we have only two strings in the parts, they will be the <em>initial</em> and <em>final</em> ones :
441     * 
442     * <pre>
443     * substring( "sn", "The", "ion" )).toString()
444     * </pre>
445     * would result in the string:
446     * <pre>
447     * (sn=The*ion)
448     * </pre>
449     * 
450     * @param attribute The attribute to use in the filter
451     * @param parts The sub elements to use in the filter
452     * @return A new FilterBuilder
453     */
454    public static FilterBuilder substring( String attribute, String... parts )
455    {
456        if ( ( parts == null ) || ( parts.length == 0 ) )
457        {
458            throw new IllegalArgumentException( "At least one if 'initial', 'any' or 'final' part is needed" );
459        }
460
461        return new FilterBuilder( SubstringFilter.substring( attribute, parts ) );
462    }
463
464
465    /**
466     * Returns the string version of the filter represented by this FilterBuilder.
467     * 
468     * @return The string representation of the filter
469     */
470    @Override
471    public String toString()
472    {
473        return filter.build().toString();
474    }
475}