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
022import org.apache.directory.api.i18n.I18n;
023
024/**
025 * A builder for constructing well formed search filters according to
026 * <a href="https://tools.ietf.org/html/rfc4515.html">RFC 4515</a>.  This 
027 * builder is most convenient when you use static imports.  For example:
028 * <pre>
029 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.and;
030 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
031 * import static org.apache.directory.ldap.client.api.search.FilterBuilder.or;
032 * 
033 * ...
034 * 
035 *         String filter = 
036 *                 or(
037 *                     and( 
038 *                         equal( "givenName", "kermit" ), 
039 *                         equal( "sn", "the frog" ) ),
040 *                     and( 
041 *                         equal( "givenName", "miss" ), 
042 *                         equal( "sn", "piggy" ) ) )
043 *                 .toString()
044 * </pre>
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class FilterBuilder
049{
050    /** The built filter */
051    /* No qualifier */ Filter filter;
052
053
054    /**
055     * A private constructor that creates a new instance of a FilterBuilder
056     * containing a given filter.
057     * 
058     * @param filter The filter
059     */
060    /* No qualifier*/ FilterBuilder( Filter filter )
061    {
062        this.filter = filter;
063    }
064
065
066    /**
067     * Returns a new FilterBuilder that will <code>&amp;</code> together all of the 
068     * supplied filters.  For example:
069     * 
070     * <pre>
071     * and( equal( "givenName", "kermit" ), equal( "sn", "the frog" ) ).toString()
072     * </pre>
073     * would result in the string:
074     * <pre>
075     * (&amp;(givenName=kermit)(sn=the frog))
076     * </pre>
077     * 
078     * Which would match all entries with a given name of <code>kermit</code>
079     * and a surname <code>the frog</code>.
080     *
081     * @param filters The filters to and together
082     * @return A new FilterBuilder
083     */
084    public static FilterBuilder and( FilterBuilder... filters )
085    {
086        SetOfFiltersFilter filter = SetOfFiltersFilter.and();
087
088        for ( FilterBuilder builder : filters )
089        {
090            filter.add( builder.filter );
091        }
092
093        return new FilterBuilder( filter );
094    }
095
096
097    /**
098     * Returns a new FilterBuilder for testing the approximate equality of an 
099     * 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}