1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 1999 by Sun Microsystems, Inc. 23 * All rights reserved. 24 * 25 */ 26 27 // SLPV1SSrvMsg.java: SLPv1 server side service rqst/reply. 28 // Author: James Kempf 29 // Created On: Thu Sep 10 15:33:58 1998 30 // Last Modified By: James Kempf 31 // Last Modified On: Fri Nov 6 14:03:00 1998 32 // Update Count: 41 33 // 34 35 36 package com.sun.slp; 37 38 import java.util.*; 39 import java.io.*; 40 41 42 /** 43 * The SLPV1SSrvMsg class models the SLP server side service request message. 44 * 45 * @author James Kempf 46 */ 47 48 class SLPV1SSrvMsg extends SSrvMsg { 49 50 // For eating whitespace. 51 52 final static char SPACE = ' '; 53 54 // Comma for list parsing. 55 56 final static char COMMA = ','; 57 58 // Logical operators. 59 60 final static char OR_OP = '|'; 61 final static char AND_OP = '&'; 62 63 // Logical operator corner case needs this. 64 65 final static char HASH = '#'; 66 67 // Comparison/Assignment operators. 68 69 final static char EQUAL_OP = '='; 70 final static char NOT_OP = '!'; 71 final static char LESS_OP = '<'; 72 final static char GREATER_OP = '>'; 73 final static char GEQUAL_OP = 'g'; 74 final static char LEQUAL_OP = 'l'; 75 76 // Parens. 77 78 final static char OPEN_PAREN = '('; 79 final static char CLOSE_PAREN = ')'; 80 81 // LDAP present operator 82 83 final static char PRESENT = '*'; 84 85 // Wildcard operator. 86 87 final static String WILDCARD = "*"; 88 89 // Character code for parsing. 90 91 String charCode = IANACharCode.UTF8; 92 93 // For creating a null reply. 94 95 protected SLPV1SSrvMsg() {} 96 97 // Construct a SLPV1SSrvMsg from the input stream. 98 99 SLPV1SSrvMsg(SrvLocHeader hdr, DataInputStream dis) 100 throws ServiceLocationException, IOException { 101 super(hdr, dis); 102 103 } 104 105 // Construct an empty SLPV1SSrvMsg, for monolingual off. 106 107 static SrvLocMsg makeEmptyReply(SLPHeaderV1 hdr) 108 throws ServiceLocationException { 109 110 SLPV1SSrvMsg msg = new SLPV1SSrvMsg(); 111 msg.hdr = hdr; 112 113 msg.makeReply(new Hashtable(), null); 114 115 return msg; 116 117 } 118 119 // Initialize the message from the input stream. 120 121 void initialize(DataInputStream dis) 122 throws ServiceLocationException, IOException { 123 124 SLPHeaderV1 hdr = (SLPHeaderV1)getHeader(); 125 StringBuffer buf = new StringBuffer(); 126 127 // First get the previous responder. 128 129 hdr.parsePreviousRespondersIn(dis); 130 131 // Now get the raw query. 132 133 hdr.getString(buf, dis); 134 135 String rq = buf.toString(); 136 137 // Parse the raw query to pull out the service type, scope, 138 // and query. 139 140 StringTokenizer st = new StringTokenizer(rq, "/", true); 141 142 try { 143 144 String type = 145 Defaults.SERVICE_PREFIX + ":" + 146 st.nextToken().trim().toLowerCase() + ":"; 147 148 serviceType = 149 hdr.checkServiceType(type); 150 151 st.nextToken(); // get rid of slash. 152 153 // Get the scope. 154 155 String scope = st.nextToken().trim().toLowerCase(); 156 157 // Special case if scope is empty (meaning the next 158 // token will be a slash). 159 160 if (scope.equals("/")) { 161 scope = ""; 162 163 } else { 164 165 st.nextToken(); // get rid of slash. 166 167 if (scope.length() > 0) { 168 169 // Validate the scope name. 170 171 hdr.validateScope(scope); 172 } 173 } 174 175 // Set up scopes vector. 176 177 hdr.scopes = new Vector(); 178 179 // Substitute default scope here. 180 181 if (scope.length() <= 0) { 182 scope = Defaults.DEFAULT_SCOPE; 183 184 } 185 186 hdr.scopes.addElement(scope.toLowerCase().trim()); 187 188 // Parsing the query is complicated by opaques having slashes. 189 190 String q = ""; 191 192 while (st.hasMoreTokens()) { 193 q = q + st.nextToken(); 194 195 } 196 197 // Drop off the final backslash, error if none. 198 199 if (!q.endsWith("/")) { 200 throw 201 new ServiceLocationException( 202 ServiceLocationException.PARSE_ERROR, 203 "v1_query_error", 204 new Object[] {rq}); 205 } 206 207 query = q.substring(0, q.length()-1); 208 209 // Save header char code for parsing. 210 211 charCode = hdr.charCode; 212 213 // Convert the query into a V2 query. 214 215 convertQuery(); 216 217 // If the query is for "service:directory-agent", then we 218 // mark it as having been multicast, because that is the 219 // only kind of multicast that we accept for SLPv1. Anybody 220 // who unicasts this to us will time out. 221 222 if (serviceType.equals(Defaults.DA_SERVICE_TYPE.toString())) { 223 hdr.mcast = true; 224 225 } 226 227 // Construct description. 228 229 hdr.constructDescription("SrvRqst", 230 " service type=``" + 231 serviceType + "''\n" + 232 " query=``" + 233 query + "''"); 234 235 } catch (NoSuchElementException ex) { 236 throw 237 new ServiceLocationException( 238 ServiceLocationException.PARSE_ERROR, 239 "v1_query_error", 240 new Object[] {rq}); 241 } 242 } 243 244 // Make a reply message. 245 246 SrvLocMsg makeReply(Hashtable urltable, 247 Hashtable URLSignatures) 248 throws ServiceLocationException { 249 250 SLPHeaderV1 hdr = 251 ((SLPHeaderV1)getHeader()).makeReplyHeader(); 252 253 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 254 255 // Edit out abstract types and nonService: URLs. 256 257 Enumeration en = urltable.keys(); 258 Vector urls = new Vector(); 259 260 while (en.hasMoreElements()) { 261 ServiceURL surl = (ServiceURL)en.nextElement(); 262 263 // Reject if abstract type or nonservice: URL. 264 265 ServiceType type = surl.getServiceType(); 266 267 if (!type.isAbstractType() && type.isServiceURL()) { 268 urls.addElement(surl); 269 270 } 271 } 272 273 hdr.iNumReplies = urls.size(); 274 // keep this info so SAs can drop 0 replies 275 276 int n = urls.size(); 277 278 // Write out the size of the list. 279 280 hdr.putInt(n, baos); 281 282 en = urls.elements(); 283 284 // Write out the size of the list. 285 286 while (en.hasMoreElements()) { 287 ServiceURL surl = (ServiceURL)en.nextElement(); 288 289 hdr.parseServiceURLOut(surl, true, baos); 290 291 } 292 293 // We ignore the signatures because we only do V1 compatibility 294 // for nonprotected scopes. 295 296 hdr.payload = baos.toByteArray(); 297 298 hdr.constructDescription("SrvRply", 299 " service URLs=``" + urls + "''\n"); 300 301 return hdr; 302 303 } 304 305 // Convert the query to a V2 query. 306 307 void convertQuery() 308 throws ServiceLocationException { 309 310 // Check for empty query. 311 312 query = query.trim(); 313 314 if (query.length() <= 0) { 315 return; 316 317 } 318 319 // Check for query join. 320 321 if (!(query.startsWith("(") && query.endsWith(")"))) { 322 323 // Rewrite to a standard query. 324 325 query = rewriteQueryJoin(query); 326 327 } 328 329 // Now rewrite the query into v2 format. 330 331 query = rewriteQuery(query); 332 } 333 334 335 // Rewrite a query join as a conjunction. 336 337 private String rewriteQueryJoin(String query) 338 throws ServiceLocationException { 339 340 // Turn infix expression into prefix. 341 342 StringBuffer sbuf = new StringBuffer(); 343 StringTokenizer tk = new StringTokenizer(query, ",", true); 344 boolean lastTokComma = true; 345 int numEx = 0; 346 347 while (tk.hasMoreElements()) { 348 String exp = tk.nextToken().trim(); 349 350 if (exp.equals(",")) { 351 if (lastTokComma) { 352 throw 353 new ServiceLocationException( 354 ServiceLocationException.PARSE_ERROR, 355 "v1_query_error", 356 new Object[] {query}); 357 358 } else { 359 lastTokComma = true; 360 } 361 362 } else { 363 lastTokComma = false; 364 365 if (exp.length() <= 0) { 366 throw 367 new ServiceLocationException( 368 ServiceLocationException.PARSE_ERROR, 369 "v1_query_error", 370 new Object[] {query}); 371 372 } 373 374 // Put in parens 375 376 sbuf.append("("); 377 sbuf.append(exp); 378 sbuf.append(")"); 379 380 numEx++; 381 } 382 } 383 384 if (lastTokComma || numEx == 0) { 385 throw 386 new ServiceLocationException( 387 ServiceLocationException.PARSE_ERROR, 388 "v1_query_error", 389 new Object[] {query}); 390 391 } 392 393 if (numEx > 1) { 394 sbuf.insert(0, "(&"); 395 sbuf.append(")"); 396 397 } 398 399 return sbuf.toString(); 400 } 401 402 // Rewrite a v1 query into v2 format. This includes character escaping. 403 404 private String rewriteQuery(String whereList) 405 throws ServiceLocationException { 406 407 // Parse a logical expression. 408 409 StreamTokenizer tk = 410 new StreamTokenizer(new StringReader(whereList)); 411 412 tk.resetSyntax(); // make all chars ordinary... 413 tk.whitespaceChars('\000','\037'); 414 tk.ordinaryChar(SPACE); // but beware of embedded whites... 415 tk.wordChars('!', '%'); 416 tk.ordinaryChar(AND_OP); 417 tk.wordChars('\'', '\''); 418 tk.ordinaryChar(OPEN_PAREN); 419 tk.ordinaryChar(CLOSE_PAREN); 420 tk.wordChars('*', '{'); 421 tk.ordinaryChar(OR_OP); 422 tk.wordChars('}', '~'); 423 424 // Initialize parse tables in terminal. 425 426 tk.ordinaryChar(EQUAL_OP); 427 tk.ordinaryChar(NOT_OP); 428 tk.ordinaryChar(LESS_OP); 429 tk.ordinaryChar(GREATER_OP); 430 431 StringBuffer buf = new StringBuffer(); 432 433 434 // Parse through the expression. 435 436 try { 437 parseInternal(tk, buf, true); 438 439 } catch (IOException ex) { 440 throw 441 new ServiceLocationException( 442 ServiceLocationException.PARSE_ERROR, 443 "v1_query_error", 444 new Object[] {query}); 445 446 } 447 448 return buf.toString(); 449 } 450 451 // Do the actual parsing, using the passed-in stream tokenizer. 452 453 private void 454 parseInternal(StreamTokenizer tk, StringBuffer buf, boolean start) 455 throws ServiceLocationException, IOException { 456 457 int tok = 0; 458 boolean ret = true; 459 460 do { 461 tok = eatWhite(tk); 462 463 // We should be at the beginning a parenthesized 464 // where list. 465 466 if (tok == OPEN_PAREN) { 467 468 // Get the next token. Eat whitespace in the process. 469 470 tok = eatWhite(tk); 471 472 // If it's a logOp, then process as a logical expression. 473 // This handles the following nasty case: 474 // 475 // (,-==the rest of it) 476 477 int logOp = tok; 478 479 if (logOp == AND_OP) { 480 481 // Need to check for escape as first thing. 482 483 tok = tk.nextToken(); 484 String str = tk.sval; // not used if token not a string... 485 tk.pushBack(); 486 487 if (tok == StreamTokenizer.TT_WORD) { 488 489 if (str.charAt(0) != HASH) { 490 parseLogicalExpression(logOp, tk, buf); 491 492 } else { 493 parse(tk, buf, true); 494 // cause we can't push back twice 495 496 } 497 498 } else { 499 parseLogicalExpression(logOp, tk, buf); 500 501 } 502 503 break; 504 505 } else if (logOp == OR_OP) { 506 507 parseLogicalExpression(logOp, tk, buf); 508 509 break; 510 511 } else { 512 513 // It's a terminal expression. Push back the last token 514 // and parse the terminal. 515 516 tk.pushBack(); 517 518 parse(tk, buf, false); 519 520 break; 521 522 } 523 524 } else { 525 throw 526 new ServiceLocationException( 527 ServiceLocationException.PARSE_ERROR, 528 "v1_query_error", 529 new Object[] {query}); 530 } 531 532 } while (true); 533 534 // Since terminals are allowed alone at the top level, 535 // we need to check here whether anything else is 536 // in the query. 537 538 if (start) { 539 540 tok = eatWhite(tk); 541 542 if (tok != StreamTokenizer.TT_EOF) { 543 544 // The line should have ended by now. 545 546 throw 547 new ServiceLocationException( 548 ServiceLocationException.PARSE_ERROR, 549 "v1_query_error", 550 new Object[] {query}); 551 } 552 } 553 554 } 555 556 // Rewrite a logical expression. 557 558 private void 559 parseLogicalExpression(int logOp, StreamTokenizer tk, StringBuffer buf) 560 throws ServiceLocationException, IOException { 561 562 // Append paren and operator to buffer. 563 564 buf.append((char)OPEN_PAREN); 565 buf.append((char)logOp); 566 567 int tok = 0; 568 569 do { 570 571 tok = eatWhite(tk); 572 573 if (tok == OPEN_PAREN) { 574 575 // So parseInternal() sees a parenthesized list. 576 577 tk.pushBack(); 578 579 // Go back to parseInternal. 580 581 parseInternal(tk, buf, false); 582 583 } else if (tok == CLOSE_PAREN) { 584 585 // Append the character to the buffer and return. 586 587 buf.append((char)tok); 588 589 return; 590 591 } else { 592 throw 593 new ServiceLocationException( 594 ServiceLocationException.PARSE_ERROR, 595 "v1_query_error", 596 new Object[] {query}); 597 } 598 599 } while (tok != StreamTokenizer.TT_EOF); 600 601 // Error if we've not caught ourselves before this. 602 603 throw 604 new ServiceLocationException( 605 ServiceLocationException.PARSE_ERROR, 606 "v1_query_error", 607 new Object[] {query}); 608 } 609 610 // Parse a terminal. Opening paren has been got. 611 612 private void parse(StreamTokenizer tk, 613 StringBuffer buf, 614 boolean firstEscaped) 615 throws ServiceLocationException, IOException { 616 617 String tag = ""; 618 int tok = 0; 619 620 tok = eatWhite(tk); 621 622 // Gather the tag and value. 623 624 if (tok != StreamTokenizer.TT_WORD) { 625 throw 626 new ServiceLocationException( 627 ServiceLocationException.PARSE_ERROR, 628 "v1_query_error", 629 new Object[] {query}); 630 } 631 632 // Parse the tag. 633 634 tag = parseTag(tk, firstEscaped); 635 636 if (tag.length() <= 0) { 637 throw 638 new ServiceLocationException( 639 ServiceLocationException.PARSE_ERROR, 640 "v1_query_error", 641 new Object[] {query}); 642 } 643 644 // Unescape tag. 645 646 tag = ServiceLocationAttributeV1.unescapeAttributeString(tag, 647 charCode); 648 649 // Now escape in v2 format, 650 651 tag = ServiceLocationAttribute.escapeAttributeString(tag, true); 652 653 // Parse the operator. 654 655 char compOp = parseOperator(tk); 656 657 // If this was a keyword operator, then add present 658 // operator and closing paren and return. 659 660 if (compOp == PRESENT) { 661 buf.append(OPEN_PAREN); 662 buf.append(tag); 663 buf.append(EQUAL_OP); 664 buf.append(PRESENT); 665 buf.append(CLOSE_PAREN); 666 return; 667 668 } 669 670 // Parse value by reading up to the next close paren. 671 // Returned value will be in v2 format. 672 673 String valTok = parseValue(tk); 674 675 // Construct the comparision depending on the operator. 676 677 if (compOp == NOT_OP) { 678 679 // If the value is an integer, we can construct a query 680 // that will exclude the number. 681 682 try { 683 684 int n = Integer.parseInt(valTok); 685 686 // Bump the integer up and down to catch numbers on both 687 // sides of the required number. Be careful not to 688 // overstep bounds. 689 690 if (n < Integer.MAX_VALUE) { 691 buf.append(OPEN_PAREN); 692 buf.append(tag); 693 buf.append(GREATER_OP); 694 buf.append(EQUAL_OP); 695 buf.append(n + 1); 696 buf.append(CLOSE_PAREN); 697 698 } 699 700 if (n > Integer.MIN_VALUE) { 701 buf.append(OPEN_PAREN); 702 buf.append(tag); 703 buf.append(LESS_OP); 704 buf.append(EQUAL_OP); 705 buf.append(n - 1); 706 buf.append(CLOSE_PAREN); 707 708 } 709 710 if ((n < Integer.MAX_VALUE) && (n > Integer.MIN_VALUE)) { 711 buf.insert(0, OR_OP); 712 buf.insert(0, OPEN_PAREN); 713 buf.append(CLOSE_PAREN); 714 715 } 716 717 } catch (NumberFormatException ex) { 718 719 // It's not an integer. We can construct a query expression 720 // that will not always work. The query rules out advertisments 721 // where the attribute value doesn't match and there are 722 // no other attributes or values, and advertisements 723 // that don't contain the attribute, but it doesn't rule out 724 // a multivalued attribute with other values or if there 725 // are other attributes. The format of the query is: 726 // "(&(<tag>=*)(!(<tag>=<value>))). 727 728 buf.append(OPEN_PAREN); 729 buf.append(AND_OP); 730 buf.append(OPEN_PAREN); 731 buf.append(tag); 732 buf.append(EQUAL_OP); 733 buf.append(PRESENT); 734 buf.append(CLOSE_PAREN); 735 buf.append(OPEN_PAREN); 736 buf.append(NOT_OP); 737 buf.append(OPEN_PAREN); 738 buf.append(tag); 739 buf.append(EQUAL_OP); 740 buf.append(valTok); 741 buf.append(CLOSE_PAREN); 742 buf.append(CLOSE_PAREN); 743 buf.append(CLOSE_PAREN); 744 745 } 746 747 } else if ((compOp == LESS_OP) || (compOp == GREATER_OP)) { 748 749 int n = 0; 750 751 try { 752 753 n = Integer.parseInt(valTok); 754 755 } catch (NumberFormatException ex) { 756 757 // It's a parse error here. 758 759 throw 760 new ServiceLocationException( 761 ServiceLocationException.PARSE_ERROR, 762 "v1_query_error", 763 new Object[] {query}); 764 765 } 766 767 // We don't attempt to handle something that would cause 768 // arithmetic overflow. 769 770 if ((n == Integer.MAX_VALUE) || (n == Integer.MIN_VALUE)) { 771 throw 772 new ServiceLocationException( 773 ServiceLocationException.PARSE_ERROR, 774 "v1_query_error", 775 new Object[] {query}); 776 777 } 778 779 // Construct a query that includes everything 780 // to the correct side. 781 782 buf.append(OPEN_PAREN); 783 buf.append(tag); 784 785 if (compOp == LESS_OP) { 786 buf.append(LESS_OP); 787 buf.append(EQUAL_OP); 788 buf.append(n - 1); 789 790 } else { 791 buf.append(GREATER_OP); 792 buf.append(EQUAL_OP); 793 buf.append(n + 1); 794 795 } 796 797 buf.append(CLOSE_PAREN); 798 799 } else { 800 801 // Simple, single operator. Just add it with the 802 // value. 803 804 buf.append(OPEN_PAREN); 805 buf.append(tag); 806 807 // Need to distinguish less and greater equal. 808 809 if (compOp == LEQUAL_OP) { 810 buf.append(LESS_OP); 811 buf.append(EQUAL_OP); 812 813 } else if (compOp == GEQUAL_OP) { 814 buf.append(GREATER_OP); 815 buf.append(EQUAL_OP); 816 817 } else { 818 buf.append(compOp); 819 820 } 821 822 buf.append(valTok); 823 buf.append(CLOSE_PAREN); 824 825 } 826 827 } 828 829 // Gather tokens with embedded whitespace and return. 830 831 private String parseTag(StreamTokenizer tk, boolean ampStart) 832 throws ServiceLocationException, IOException { 833 834 String value = ""; 835 836 // Take care of corner case here. 837 838 if (ampStart) { 839 value = value +"&"; 840 ampStart = false; 841 } 842 843 do { 844 845 if (tk.ttype == StreamTokenizer.TT_WORD) { 846 value += tk.sval; 847 848 } else if ((char)tk.ttype == SPACE) { 849 value = value + " "; 850 851 } else if ((char)tk.ttype == AND_OP) { 852 value = value + "&"; 853 854 } else { 855 break; 856 857 } 858 tk.nextToken(); 859 860 } while (true); 861 862 return value.trim(); // removes trailing whitespace... 863 } 864 865 private char parseOperator(StreamTokenizer tk) 866 throws ServiceLocationException, IOException { 867 868 int tok = tk.ttype; 869 870 // If the token is a close paren, then this was a keyword 871 // (e.g. "(foo)". Return the present operator. 872 873 if ((char)tok == CLOSE_PAREN) { 874 return PRESENT; 875 876 } 877 878 if (tok != EQUAL_OP && tok != NOT_OP && 879 tok != LESS_OP && tok != GREATER_OP) { 880 881 throw 882 new ServiceLocationException( 883 ServiceLocationException.PARSE_ERROR, 884 "v1_query_error", 885 new Object[] {query}); 886 887 } 888 889 char compOp = (char)tok; 890 891 // Get the next token. 892 893 tok = tk.nextToken(); 894 895 // Look for dual character operators. 896 897 if ((char)tok == EQUAL_OP) { 898 899 // Here, we can have either "!=", "<=", ">=", or "==". 900 // Anything else is wrong. 901 902 if (compOp != LESS_OP && compOp != GREATER_OP && 903 compOp != EQUAL_OP && compOp != NOT_OP) { 904 throw 905 new ServiceLocationException( 906 ServiceLocationException.PARSE_ERROR, 907 "v1_query_error", 908 new Object[] {query}); 909 } 910 911 // Assign the right dual operator. 912 913 if (compOp == LESS_OP) { 914 compOp = LEQUAL_OP; 915 916 } else if (compOp == GREATER_OP) { 917 compOp = GEQUAL_OP; 918 919 } 920 921 } else if (compOp != LESS_OP && compOp != GREATER_OP) { 922 923 // Error if the comparison operator was something other 924 // than ``<'' or ``>'' and there is no equal. This 925 // rules out ``!'' or ``='' alone. 926 927 throw 928 new ServiceLocationException( 929 ServiceLocationException.PARSE_ERROR, 930 "v1_query_error", 931 new Object[] {query}); 932 933 } else { 934 935 // Push back the last token if it wasn't a two character operator. 936 937 tk.pushBack(); 938 939 } 940 941 return compOp; 942 } 943 944 945 private String parseValue(StreamTokenizer tk) 946 throws ServiceLocationException, IOException { 947 948 int tok = 0; 949 StringBuffer valTok = new StringBuffer(); 950 951 // Eat leading whitespace. 952 953 tok = eatWhite(tk); 954 955 // If the first value is a paren, then we've got an 956 // opaque. 957 958 if ((char)tok == OPEN_PAREN) { 959 960 valTok.append("("); 961 962 // Collect all tokens up to the closing paren. 963 964 do { 965 966 tok = tk.nextToken(); 967 968 // It's a closing paren. break out of the loop. 969 970 if ((char)tok == CLOSE_PAREN) { 971 valTok.append(")"); 972 break; 973 974 } else if ((char)tok == EQUAL_OP) { 975 valTok.append("="); 976 977 } else if (tok == StreamTokenizer.TT_WORD) { 978 valTok.append(tk.sval); 979 980 } else { 981 throw 982 new ServiceLocationException( 983 ServiceLocationException.PARSE_ERROR, 984 "v1_query_error", 985 new Object[] {query}); 986 } 987 988 } while (true); 989 990 991 // Eat whitespace until closing paren. 992 993 tok = eatWhite(tk); 994 995 if ((char)tok != CLOSE_PAREN) { 996 throw 997 new ServiceLocationException( 998 ServiceLocationException.PARSE_ERROR, 999 "v1_query_error", 1000 new Object[] {query}); 1001 1002 } 1003 1004 } else { 1005 1006 // Error if just a closed paren. 1007 1008 if (tok == CLOSE_PAREN) { 1009 throw 1010 new ServiceLocationException( 1011 ServiceLocationException.PARSE_ERROR, 1012 "v1_query_error", 1013 new Object[] {query}); 1014 1015 } 1016 1017 do { 1018 1019 // Append the token if a WORD 1020 1021 if (tok == StreamTokenizer.TT_WORD) { 1022 valTok.append(tk.sval); 1023 1024 } else if ((tok != StreamTokenizer.TT_EOF) && 1025 (tok != StreamTokenizer.TT_EOL) && 1026 (tok != CLOSE_PAREN)) { 1027 1028 // Otherwise, it's a token char, so append. 1029 1030 valTok.append((char)tok); 1031 1032 } 1033 1034 tok = tk.nextToken(); 1035 1036 } while (tok != CLOSE_PAREN); 1037 } 1038 1039 // If a wildcard, remove wildcard stars here for later re-insertion. 1040 1041 String strval = valTok.toString().trim(); 1042 boolean wildstart = false; 1043 boolean wildend = false; 1044 1045 if (strval.startsWith(WILDCARD)) { 1046 wildstart = true; 1047 strval = strval.substring(1, strval.length()); 1048 1049 } 1050 1051 if (strval.endsWith(WILDCARD)) { 1052 wildend = true; 1053 strval = strval.substring(0, strval.length()-1); 1054 1055 } 1056 1057 // Evaluate the value. 1058 1059 Object val = 1060 ServiceLocationAttributeV1.evaluate(strval, charCode); 1061 1062 // Now convert to v2 format, and return. 1063 1064 if (val instanceof String) { 1065 strval = 1066 ServiceLocationAttribute.escapeAttributeString(val.toString(), 1067 false); 1068 1069 // Add wildcards back in. 1070 1071 if (wildstart) { 1072 strval = WILDCARD + strval; 1073 1074 } 1075 1076 if (wildend) { 1077 strval = strval + WILDCARD; 1078 1079 } 1080 1081 } else { 1082 strval = val.toString(); 1083 1084 } 1085 1086 return strval; 1087 1088 } 1089 1090 // Eat whitespace. 1091 1092 private int eatWhite(StreamTokenizer tk) 1093 throws IOException { 1094 1095 int tok = tk.nextToken(); 1096 1097 while (tok == SPACE) { 1098 tok = tk.nextToken(); 1099 1100 } 1101 1102 return tok; 1103 } 1104 } 1105