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>&</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 * (&(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>=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>=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<=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}