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.shared.ldap.model.filter; 021 022 023import java.text.ParseException; 024 025import org.apache.directory.shared.i18n.I18n; 026import org.apache.directory.shared.ldap.model.entry.AttributeUtils; 027import org.apache.directory.shared.ldap.model.entry.BinaryValue; 028import org.apache.directory.shared.ldap.model.entry.Value; 029import org.apache.directory.shared.ldap.model.exception.LdapException; 030import org.apache.directory.shared.ldap.model.schema.AttributeType; 031import org.apache.directory.shared.ldap.model.schema.SchemaManager; 032import org.apache.directory.shared.util.Chars; 033import org.apache.directory.shared.util.Hex; 034import org.apache.directory.shared.util.Position; 035import org.apache.directory.shared.util.Strings; 036import org.apache.directory.shared.util.Unicode; 037 038 039/** 040 * This class parse a Ldap filter. The grammar is given in RFC 4515 041 * 042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 043 */ 044public class FilterParser 045{ 046 /** 047 * Creates a filter parser implementation. 048 */ 049 public FilterParser() 050 { 051 } 052 053 054 /** 055 * Parse an extensible 056 * 057 * extensible = ( attr [":dn"] [':' oid] ":=" assertionvalue ) 058 * / ( [":dn"] ':' oid ":=" assertionvalue ) 059 * matchingrule = ":" oid 060 */ 061 private static ExprNode parseExtensible( SchemaManager schemaManager, String attribute, String filter, Position pos ) throws LdapException, ParseException 062 { 063 ExtensibleNode node = null; 064 065 if ( schemaManager != null ) 066 { 067 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 068 069 if ( attributeType != null ) 070 { 071 node = new ExtensibleNode( attributeType ); 072 } 073 else 074 { 075 return UndefinedNode.UNDEFINED_NODE; 076 } 077 } 078 else 079 { 080 node = new ExtensibleNode( attribute ); 081 } 082 083 if ( attribute != null ) 084 { 085 // First check if we have a ":dn" 086 if ( Strings.areEquals( filter, pos.start, "dn" ) ) 087 { 088 // Set the dnAttributes flag and move forward in the string 089 node.setDnAttributes( true ); 090 pos.start += 2; 091 } 092 else 093 { 094 // Push back the ':' 095 pos.start--; 096 } 097 098 // Do we have a MatchingRule ? 099 if ( Strings.charAt(filter, pos.start) == ':' ) 100 { 101 pos.start++; 102 int start = pos.start; 103 104 if ( Strings.charAt(filter, pos.start) == '=' ) 105 { 106 pos.start++; 107 108 // Get the assertionValue 109 node.setValue( parseAssertionValue( filter, pos ) ); 110 111 return node; 112 } 113 else 114 { 115 AttributeUtils.parseAttribute( filter, pos, false ); 116 117 node.setMatchingRuleId( filter.substring( start, pos.start ) ); 118 119 if ( Strings.areEquals( filter, pos.start, ":=" ) ) 120 { 121 pos.start += 2; 122 123 // Get the assertionValue 124 node.setValue( parseAssertionValue( filter, pos ) ); 125 126 return node; 127 } 128 else 129 { 130 throw new ParseException( I18n.err( I18n.ERR_04146 ), pos.start ); 131 } 132 } 133 } 134 else 135 { 136 throw new ParseException( I18n.err( I18n.ERR_04147 ), pos.start ); 137 } 138 } 139 else 140 { 141 boolean oidRequested = false; 142 143 // First check if we have a ":dn" 144 if ( Strings.areEquals( filter, pos.start, ":dn" ) ) 145 { 146 // Set the dnAttributes flag and move forward in the string 147 node.setDnAttributes( true ); 148 pos.start += 3; 149 } 150 else 151 { 152 oidRequested = true; 153 } 154 155 // Do we have a MatchingRule ? 156 if ( Strings.charAt(filter, pos.start) == ':' ) 157 { 158 pos.start++; 159 int start = pos.start; 160 161 if ( Strings.charAt(filter, pos.start) == '=' ) 162 { 163 if ( oidRequested ) 164 { 165 throw new ParseException( I18n.err( I18n.ERR_04148 ), pos.start ); 166 } 167 168 pos.start++; 169 170 // Get the assertionValue 171 node.setValue( parseAssertionValue( filter, pos ) ); 172 173 return node; 174 } 175 else 176 { 177 AttributeUtils.parseAttribute(filter, pos, false); 178 179 node.setMatchingRuleId( filter.substring( start, pos.start ) ); 180 181 if ( Strings.areEquals( filter, pos.start, ":=" ) ) 182 { 183 pos.start += 2; 184 185 // Get the assertionValue 186 node.setValue( parseAssertionValue( filter, pos ) ); 187 188 return node; 189 } 190 else 191 { 192 throw new ParseException( I18n.err( I18n.ERR_04146 ), pos.start ); 193 } 194 } 195 } 196 else 197 { 198 throw new ParseException( I18n.err( I18n.ERR_04147 ), pos.start ); 199 } 200 } 201 } 202 203 204 /** 205 * An assertion value : 206 * assertionvalue = valueencoding 207 * valueencoding = 0*(normal / escaped) 208 * normal = UTF1SUBSET / UTFMB 209 * escaped = '\' HEX HEX 210 * HEX = '0'-'9' / 'A'-'F' / 'a'-'f' 211 * UTF1SUBSET = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\') 212 * UTFMB = UTF2 / UTF3 / UTF4 213 * UTF0 = %x80-BF 214 * UTF2 = %xC2-DF UTF0 215 * UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0 216 * UTF4 = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0 217 * 218 * With the specific constraints (RFC 4515): 219 * "The <valueencoding> rule ensures that the entire filter string is a" 220 * "valid UTF-8 string and provides that the octets that represent the" 221 * "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII" 222 * "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a" 223 * "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits" 224 * "representing the value of the encoded octet." 225 226 * 227 * The incomming String is already transformed from UTF-8 to unicode, so we must assume that the 228 * grammar we have to check is the following : 229 * 230 * assertionvalue = valueencoding 231 * valueencoding = 0*(normal / escaped) 232 * normal = unicodeSubset 233 * escaped = '\' HEX HEX 234 * HEX = '0'-'9' / 'A'-'F' / 'a'-'f' 235 * unicodeSubset = %x01-27 / %x2B-5B / %x5D-FFFF 236 */ 237 private static Value<?> parseAssertionValue( String filter, Position pos ) throws ParseException 238 { 239 char c = Strings.charAt(filter, pos.start); 240 241 // Create a buffer big enough to contain the value once converted 242 byte[] value = new byte[ filter.length() - pos.start]; 243 int current = 0; 244 245 do 246 { 247 if ( Unicode.isUnicodeSubset(c) ) 248 { 249 value[current++] = (byte)c; 250 pos.start++; 251 } 252 else if ( Strings.isCharASCII( filter, pos.start, '\\' ) ) 253 { 254 // Maybe an escaped 255 pos.start++; 256 257 // First hex 258 if ( Chars.isHex(filter, pos.start) ) 259 { 260 pos.start++; 261 } 262 else 263 { 264 throw new ParseException( I18n.err( I18n.ERR_04149 ), pos.start ); 265 } 266 267 // second hex 268 if ( Chars.isHex(filter, pos.start) ) 269 { 270 value[current++] = Hex.getHexValue(filter.charAt(pos.start - 1), filter.charAt(pos.start)); 271 pos.start++; 272 } 273 else 274 { 275 throw new ParseException( I18n.err( I18n.ERR_04149 ), pos.start ); 276 } 277 } 278 else 279 { 280 // not a valid char, so let's get out 281 break; 282 } 283 } 284 while ( ( c = Strings.charAt(filter, pos.start) ) != '\0' ); 285 286 if ( current != 0 ) 287 { 288 byte[] result = new byte[ current ]; 289 System.arraycopy( value, 0, result, 0, current ); 290 291 return new BinaryValue( result ); 292 } 293 else 294 { 295 return new BinaryValue( (byte[])null ); 296 } 297 } 298 299 300 /** 301 * Parse a substring 302 */ 303 private static ExprNode parseSubstring( SchemaManager schemaManager, String attribute, Value<?> initial, String filter, Position pos ) 304 throws ParseException, LdapException 305 { 306 if ( Strings.isCharASCII( filter, pos.start, '*' ) ) 307 { 308 // We have found a '*' : this is a substring 309 SubstringNode node = null; 310 311 if ( schemaManager != null ) 312 { 313 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute ); 314 315 if ( attributeType != null ) 316 { 317 node = new SubstringNode( schemaManager.lookupAttributeTypeRegistry( attribute ) ); 318 } 319 else 320 { 321 return null; 322 } 323 } 324 else 325 { 326 node = new SubstringNode( attribute ); 327 } 328 329 if ( ( initial != null ) && !initial.isNull() ) 330 { 331 // We have a substring starting with a value : val*... 332 // Set the initial value. It must be a String 333 String initialStr = initial.getString(); 334 node.setInitial( initialStr ); 335 } 336 337 pos.start++; 338 339 // 340 while ( true ) 341 { 342 Value<?> assertionValue = parseAssertionValue( filter, pos ); 343 344 // Is there anything else but a ')' after the value ? 345 if ( Strings.isCharASCII( filter, pos.start, ')' ) ) 346 { 347 // Nope : as we have had [initial] '*' (any '*' ) *, 348 // this is the final 349 if ( !assertionValue.isNull() ) 350 { 351 String finalStr = assertionValue.getString(); 352 node.setFinal( finalStr ); 353 } 354 355 return node; 356 } 357 else if ( Strings.isCharASCII( filter, pos.start, '*' ) ) 358 { 359 // We have a '*' : it's an any 360 // If the value is empty, that means we have more than 361 // one consecutive '*' : do nothing in this case. 362 if ( !assertionValue.isNull() ) 363 { 364 String anyStr = assertionValue.getString(); 365 node.addAny( anyStr ); 366 } 367 368 pos.start++; 369 } 370 else 371 { 372 // This is an error 373 throw new ParseException( I18n.err( I18n.ERR_04150 ), pos.start ); 374 } 375 } 376 } 377 else 378 { 379 // This is an error 380 throw new ParseException( I18n.err( I18n.ERR_04150 ), pos.start ); 381 } 382 } 383 384 385 /** 386 * Here is the grammar to parse : 387 * 388 * simple ::= '=' assertionValue 389 * present ::= '=' '*' 390 * substring ::= '=' [initial] any [final] 391 * initial ::= assertionValue 392 * any ::= '*' ( assertionValue '*')* 393 * 394 * As we can see, there is an ambiguity in the grammar : attr=* can be 395 * seen as a present or as a substring. As stated in the RFC : 396 * 397 * "Note that although both the <substring> and <present> productions in" 398 * "the grammar above can produce the "attr=*" construct, this construct" 399 * "is used only to denote a presence filter." (RFC 4515, 3) 400 * 401 * We have also to consider the difference between a substring and the 402 * equality node : this last node does not contain a '*' 403 * 404 * @param attributeType 405 * @param filter 406 * @param pos 407 * @return 408 */ 409 @SuppressWarnings({ "rawtypes", "unchecked" }) 410 private static ExprNode parsePresenceEqOrSubstring( SchemaManager schemaManager, String attribute, String filter, Position pos ) 411 throws ParseException, LdapException 412 { 413 if ( Strings.isCharASCII( filter, pos.start, '*' ) ) 414 { 415 // To be a present node, the next char should be a ')' 416 pos.start++; 417 418 if ( Strings.isCharASCII( filter, pos.start, ')' ) ) 419 { 420 // This is a present node 421 if ( schemaManager != null ) 422 { 423 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 424 425 if ( attributeType != null ) 426 { 427 return new PresenceNode( attributeType ); 428 } 429 else 430 { 431 return null; 432 } 433 } 434 else 435 { 436 return new PresenceNode( attribute ); 437 } 438 } 439 else 440 { 441 // Definitively a substring with no initial or an error 442 // Push back the '*' on the string 443 pos.start--; 444 return parseSubstring( schemaManager, attribute, null, filter, pos ); 445 } 446 } 447 else if ( Strings.isCharASCII( filter, pos.start, ')' ) ) 448 { 449 // An empty equality Node 450 if ( schemaManager != null ) 451 { 452 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 453 454 if ( attributeType != null ) 455 { 456 return new EqualityNode( attributeType, new BinaryValue( (byte[])null ) ); 457 } 458 459 else 460 { 461 return null; 462 } 463 } 464 else 465 { 466 return new EqualityNode( attribute, new BinaryValue( (byte[])null ) ); 467 } 468 } 469 else 470 { 471 // A substring or an equality node 472 Value<?> value = parseAssertionValue( filter, pos ); 473 474 // Is there anything else but a ')' after the value ? 475 if ( Strings.isCharASCII( filter, pos.start, ')' ) ) 476 { 477 // This is an equality node 478 if ( schemaManager != null ) 479 { 480 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 481 482 if ( attributeType != null ) 483 { 484 return new EqualityNode( attributeType, value ); 485 } 486 else 487 { 488 return null; 489 } 490 } 491 else 492 { 493 return new EqualityNode( attribute, value ); 494 } 495 } 496 497 return parseSubstring( schemaManager, attribute, value, filter, pos ); 498 } 499 } 500 501 502 /** 503 * Parse the following grammar : 504 * item = simple / present / substring / extensible 505 * simple = attr filtertype assertionvalue 506 * filtertype = '=' / '~=' / '>=' / '<=' 507 * present = attr '=' '*' 508 * substring = attr '=' [initial] any [final] 509 * extensible = ( attr [":dn"] [':' oid] ":=" assertionvalue ) 510 * / ( [":dn"] ':' oid ":=" assertionvalue ) 511 * matchingrule = ":" oid 512 * 513 * An item starts with an attribute or a colon. 514 */ 515 @SuppressWarnings({ "rawtypes", "unchecked" }) 516 private static ExprNode parseItem( SchemaManager schemaManager, String filter, Position pos, char c ) 517 throws ParseException, LdapException 518 { 519 String attribute = null; 520 521 if ( c == '\0' ) 522 { 523 throw new ParseException( I18n.err( I18n.ERR_04151 ), pos.start ); 524 } 525 526 if ( c == ':' ) 527 { 528 // If we have a colon, then the item is an extensible one 529 return parseExtensible( schemaManager, null, filter, pos ); 530 } 531 else 532 { 533 // We must have an attribute 534 attribute = AttributeUtils.parseAttribute( filter, pos, true ); 535 536 // Now, we may have a present, substring, simple or an extensible 537 c = Strings.charAt(filter, pos.start); 538 539 switch ( c ) 540 { 541 case '=': 542 // It can be a presence, an equal or a substring 543 pos.start++; 544 return parsePresenceEqOrSubstring( schemaManager, attribute, filter, pos ); 545 546 case '~': 547 // Approximate node 548 pos.start++; 549 550 // Check that we have a '=' 551 if ( !Strings.isCharASCII( filter, pos.start, '=' ) ) 552 { 553 throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start ); 554 } 555 556 pos.start++; 557 558 // Parse the value and create the node 559 if ( schemaManager == null ) 560 { 561 return new ApproximateNode( attribute, parseAssertionValue( filter, pos ) ); 562 } 563 else 564 { 565 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 566 567 if ( attributeType != null ) 568 { 569 return new ApproximateNode( attributeType, parseAssertionValue( filter, pos ) ); 570 } 571 else 572 { 573 return UndefinedNode.UNDEFINED_NODE; 574 } 575 } 576 577 case '>': 578 // Greater or equal node 579 pos.start++; 580 581 // Check that we have a '=' 582 if ( !Strings.isCharASCII( filter, pos.start, '=' ) ) 583 { 584 throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start ); 585 } 586 587 pos.start++; 588 589 // Parse the value and create the node 590 if ( schemaManager == null ) 591 { 592 return new GreaterEqNode( attribute, parseAssertionValue( filter, pos ) ); 593 } 594 else 595 { 596 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 597 598 if ( attributeType != null ) 599 { 600 return new GreaterEqNode( attributeType, parseAssertionValue( filter, pos ) ); 601 } 602 else 603 { 604 return UndefinedNode.UNDEFINED_NODE; 605 } 606 } 607 608 case '<': 609 // Less or equal node 610 pos.start++; 611 612 // Check that we have a '=' 613 if ( !Strings.isCharASCII( filter, pos.start, '=' ) ) 614 { 615 throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start ); 616 } 617 618 pos.start++; 619 620 // Parse the value and create the node 621 if ( schemaManager == null ) 622 { 623 return new LessEqNode( attribute, parseAssertionValue( filter, pos ) ); 624 } 625 else 626 { 627 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 628 629 if ( attributeType != null ) 630 { 631 return new LessEqNode( attributeType, parseAssertionValue( filter, pos ) ); 632 } 633 else 634 { 635 return UndefinedNode.UNDEFINED_NODE; 636 } 637 } 638 639 case ':': 640 // An extensible node 641 pos.start++; 642 return parseExtensible( schemaManager, attribute, filter, pos ); 643 644 default: 645 // This is an error 646 throw new ParseException( I18n.err( I18n.ERR_04153 ), pos.start ); 647 } 648 } 649 } 650 651 652 /** 653 * Parse AND, OR and NOT nodes : 654 * 655 * and = '&' filterlist 656 * or = '|' filterlist 657 * not = '!' filter 658 * filterlist = 1*filter 659 * 660 * @return 661 */ 662 private static ExprNode parseBranchNode( SchemaManager schemaManager, ExprNode node, String filter, Position pos ) 663 throws ParseException, LdapException 664 { 665 BranchNode branchNode = ( BranchNode ) node; 666 int nbChildren = 0; 667 668 // We must have at least one filter 669 ExprNode child = parseFilterInternal( schemaManager, filter, pos ); 670 671 if ( child != UndefinedNode.UNDEFINED_NODE ) 672 { 673 // Add the child to the node children 674 branchNode.addNode( child ); 675 676 if ( branchNode instanceof NotNode ) 677 { 678 return node; 679 } 680 681 nbChildren++; 682 } 683 else if ( node instanceof AndNode ) 684 { 685 return UndefinedNode.UNDEFINED_NODE; 686 } 687 688 // Now, iterate recusively though all the remaining filters, if any 689 while ( ( child = parseFilterInternal( schemaManager, filter, pos ) ) != UndefinedNode.UNDEFINED_NODE ) 690 { 691 // Add the child to the node children if not null 692 if ( child != null ) 693 { 694 branchNode.addNode( child ); 695 nbChildren++; 696 } 697 else if ( node instanceof AndNode ) 698 { 699 return UndefinedNode.UNDEFINED_NODE; 700 } 701 } 702 703 if ( nbChildren > 0 ) 704 { 705 return node; 706 } 707 else 708 { 709 return UndefinedNode.UNDEFINED_NODE; 710 } 711 } 712 713 714 /** 715 * filtercomp = and / or / not / item 716 * and = '&' filterlist 717 * or = '|' filterlist 718 * not = '!' filter 719 * item = simple / present / substring / extensible 720 * simple = attr filtertype assertionvalue 721 * present = attr EQUALS ASTERISK 722 * substring = attr EQUALS [initial] any [final] 723 * extensible = ( attr [dnattrs] 724 * [matchingrule] COLON EQUALS assertionvalue ) 725 * / ( [dnattrs] 726 * matchingrule COLON EQUALS assertionvalue ) 727 */ 728 private static ExprNode parseFilterComp( SchemaManager schemaManager, String filter, Position pos ) 729 throws ParseException, LdapException 730 { 731 ExprNode node = null; 732 733 if ( pos.start == pos.length ) 734 { 735 throw new ParseException( I18n.err( I18n.ERR_04154 ), pos.start ); 736 } 737 738 char c = Strings.charAt(filter, pos.start); 739 740 switch ( c ) 741 { 742 case '&': 743 // This is a AND node 744 pos.start++; 745 node = new AndNode(); 746 node = parseBranchNode( schemaManager, node, filter, pos ); 747 break; 748 749 case '|': 750 // This is an OR node 751 pos.start++; 752 node = new OrNode(); 753 node = parseBranchNode( schemaManager, node, filter, pos ); 754 break; 755 756 case '!': 757 // This is a NOT node 758 pos.start++; 759 node = new NotNode(); 760 node = parseBranchNode( schemaManager, node, filter, pos ); 761 break; 762 763 default: 764 // This is an item 765 node = parseItem( schemaManager, filter, pos, c ); 766 break; 767 768 } 769 770 return node; 771 } 772 773 774 /** 775 * Pasre the grammar rule : 776 * filter ::= '(' filterComp ')' 777 */ 778 private static ExprNode parseFilterInternal( SchemaManager schemaManager, String filter, Position pos ) 779 throws ParseException, LdapException 780 { 781 // Check for the left '(' 782 if ( !Strings.isCharASCII( filter, pos.start, '(' ) ) 783 { 784 // No more node, get out 785 if ( ( pos.start == 0 ) && ( pos.length != 0 ) ) 786 { 787 throw new ParseException( I18n.err( I18n.ERR_04155 ), 0 ); 788 } 789 else 790 { 791 return UndefinedNode.UNDEFINED_NODE; 792 } 793 } 794 795 pos.start++; 796 797 // parse the filter component 798 ExprNode node = parseFilterComp( schemaManager, filter, pos ); 799 800 if ( node == UndefinedNode.UNDEFINED_NODE ) 801 { 802 return UndefinedNode.UNDEFINED_NODE; 803 } 804 805 // Check that we have a right ')' 806 if ( !Strings.isCharASCII( filter, pos.start, ')' ) ) 807 { 808 throw new ParseException( I18n.err( I18n.ERR_04157 ), pos.start ); 809 } 810 811 pos.start++; 812 813 return node; 814 } 815 816 817 /** 818 * @see FilterParser#parse(String) 819 */ 820 public static ExprNode parse( String filter ) throws ParseException 821 { 822 return parse( null, filter ); 823 } 824 825 826 /** 827 * @see FilterParser#parse(String) 828 */ 829 public static ExprNode parse( SchemaManager schemaManager, String filter ) throws ParseException 830 { 831 // The filter must not be null. This is a defensive test 832 if ( Strings.isEmpty(filter) ) 833 { 834 throw new ParseException( I18n.err( I18n.ERR_04158 ), 0 ); 835 } 836 837 Position pos = new Position(); 838 pos.start = 0; 839 pos.end = 0; 840 pos.length = filter.length(); 841 842 try 843 { 844 return parseFilterInternal( schemaManager, filter, pos ); 845 } 846 catch ( LdapException le ) 847 { 848 throw new ParseException( le.getMessage(), pos.start ); 849 } 850 } 851 852 853 /** 854 * @see FilterParser#parse(String) 855 */ 856 public static ExprNode parse( SchemaManager schemaManager, String filter, Position pos ) throws ParseException 857 { 858 // The filter must not be null. This is a defensive test 859 if ( Strings.isEmpty(filter) ) 860 { 861 throw new ParseException( I18n.err( I18n.ERR_04158 ), 0 ); 862 } 863 864 pos.start = 0; 865 pos.end = 0; 866 pos.length = filter.length(); 867 868 try 869 { 870 return parseFilterInternal( schemaManager, filter, pos ); 871 } 872 catch ( LdapException le ) 873 { 874 throw new ParseException( le.getMessage(), pos.start ); 875 } 876 } 877}