1 package org.tigris.scarab.util.word; 2 3 48 49 import java.sql.Connection ; 51 import java.sql.ResultSet ; 52 import java.sql.SQLException ; 53 import java.sql.Statement ; 54 import java.text.DateFormat ; 55 import java.text.ParseException ; 56 import java.text.SimpleDateFormat ; 57 import java.util.ArrayList ; 58 import java.util.Date ; 59 import java.util.HashMap ; 60 import java.util.HashSet ; 61 import java.util.Iterator ; 62 import java.util.List ; 63 import java.util.Locale ; 64 import java.util.Map ; 65 import java.util.NoSuchElementException ; 66 import java.util.Set ; 67 68 import org.apache.commons.collections.map.LRUMap; 69 import org.apache.commons.collections.map.LinkedMap; 70 import org.apache.commons.lang.ObjectUtils; 71 import org.apache.commons.lang.StringUtils; 72 import org.apache.fulcrum.localization.Localization; 73 import org.apache.fulcrum.parser.ParameterParser; 74 import org.apache.fulcrum.parser.StringValueParser; 75 import org.apache.log4j.Logger; 76 import org.apache.torque.Torque; 77 import org.apache.torque.TorqueException; 78 import org.apache.torque.adapter.DB; 79 import org.apache.torque.om.ComboKey; 80 import org.apache.torque.om.ObjectKey; 81 import org.apache.torque.om.SimpleKey; 82 import org.apache.torque.util.Criteria; 83 import org.tigris.scarab.attribute.DateAttribute; 84 import org.tigris.scarab.attribute.OptionAttribute; 85 import org.tigris.scarab.attribute.StringAttribute; 86 import org.tigris.scarab.om.ActivityPeer; 87 import org.tigris.scarab.om.ActivitySetPeer; 88 import org.tigris.scarab.om.AttachmentTypePeer; 89 import org.tigris.scarab.om.Attribute; 90 import org.tigris.scarab.om.AttributeManager; 91 import org.tigris.scarab.om.AttributeValue; 92 import org.tigris.scarab.om.AttributeValuePeer; 93 import org.tigris.scarab.om.Issue; 94 import org.tigris.scarab.om.IssuePeer; 95 import org.tigris.scarab.om.IssueType; 96 import org.tigris.scarab.om.MITList; 97 import org.tigris.scarab.om.MITListItem; 98 import org.tigris.scarab.om.Module; 99 import org.tigris.scarab.om.ModuleManager; 100 import org.tigris.scarab.om.RModuleIssueType; 101 import org.tigris.scarab.om.RModuleIssueTypeManager; 102 import org.tigris.scarab.om.RModuleOption; 103 import org.tigris.scarab.om.RModuleOptionPeer; 104 import org.tigris.scarab.om.RModuleUserAttribute; 105 import org.tigris.scarab.om.ScarabUser; 106 import org.tigris.scarab.services.security.ScarabSecurity; 107 import org.tigris.scarab.tools.ScarabLocalizationTool; 108 import org.tigris.scarab.tools.localization.L10NKeySet; 109 import org.tigris.scarab.util.IteratorWithSize; 110 import org.tigris.scarab.util.Log; 111 import org.tigris.scarab.util.ScarabConstants; 112 import org.tigris.scarab.util.ScarabException; 113 114 122 public class IssueSearch 123 extends Issue 124 { 125 private static final int MAX_INNER_JOIN = 126 ScarabConstants.QUERY_MAX_FILTER_CRITERIA; 127 128 private static final int MAX_JOIN = 129 ScarabConstants.QUERY_MAX_JOIN; 130 131 public static final String ASC = "asc"; 132 public static final String DESC = "desc"; 133 134 public static final String CREATED_BY_KEY = "created_by"; 135 public static final String ANY_KEY = "any"; 136 137 private static final String AV_OPTION_ID = 139 AttributeValuePeer.OPTION_ID.substring( 140 AttributeValuePeer.OPTION_ID.indexOf('.')+1); 141 private static final String AV_ISSUE_ID = 142 AttributeValuePeer.ISSUE_ID.substring( 143 AttributeValuePeer.ISSUE_ID.indexOf('.')+1); 144 private static final String AV_USER_ID = 145 AttributeValuePeer.USER_ID.substring( 146 AttributeValuePeer.USER_ID.indexOf('.')+1); 147 148 private static final String ACTIVITYSETALIAS = "srchcobyactset"; 149 private static final String USERAVALIAS = "srchuav"; 150 private static final String ACTIVITYALIAS = "srchcobyact"; 151 152 private static final String CREATED_BY = "CREATED_BY"; 153 private static final String CREATED_DATE = "CREATED_DATE"; 154 private static final String ATTRIBUTE_ID = "ATTRIBUTE_ID"; 155 private static final String AND = " AND "; 156 private static final String OR = " OR "; 157 private static final String INNER_JOIN = " INNER JOIN "; 158 private static final String ON = " ON ("; 159 private static final String IN = " IN ("; 160 private static final String IS_NULL = " IS NULL"; 161 private static final String LEFT_OUTER_JOIN = " LEFT OUTER JOIN "; 162 private static final String SELECT_DISTINCT = "select DISTINCT "; 163 164 private static final String ACT_TRAN_ID = 165 ActivityPeer.TRANSACTION_ID.substring( 166 ActivityPeer.TRANSACTION_ID.indexOf('.')+1); 167 private static final String ACTSET_TRAN_ID = 168 ActivitySetPeer.TRANSACTION_ID.substring( 169 ActivitySetPeer.TRANSACTION_ID.indexOf('.')+1); 170 private static final String 171 ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID = 172 IssuePeer.CREATED_TRANS_ID + '=' + 173 ACTIVITYSETALIAS + '.' + ACTSET_TRAN_ID; 174 175 private static final String ACT_ISSUE_ID = 176 ActivityPeer.ISSUE_ID.substring(ActivityPeer.ISSUE_ID.indexOf('.')+1); 177 private static final String ACTIVITYALIAS_ISSUE_ID = 178 ACTIVITYALIAS + '.' + ACT_ISSUE_ID; 179 private static final String 180 ACTIVITYALIAS_ISSUE_ID__EQUALS__ISSUEPEER_ISSUE_ID = 181 ACTIVITYALIAS_ISSUE_ID + '=' + IssuePeer.ISSUE_ID; 182 private static final String END_DATE = 183 ActivityPeer.END_DATE.substring( 184 ActivityPeer.END_DATE.indexOf('.')+1); 185 186 private static final String ACT_ATTR_ID = 187 ActivityPeer.ATTRIBUTE_ID.substring( 188 ActivityPeer.ATTRIBUTE_ID.indexOf('.')+1); 189 private static final String AV_ATTR_ID = 190 AttributeValuePeer.ATTRIBUTE_ID.substring( 191 AttributeValuePeer.ATTRIBUTE_ID.indexOf('.')+1); 192 private static final String ACTIVITYALIAS_ATTRIBUTE_ID = 193 ACTIVITYALIAS + '.' + ACT_ATTR_ID; 194 195 196 private static final String USERAVALIAS_ISSUE_ID = 197 USERAVALIAS + '.' + AV_ISSUE_ID; 198 199 private static final String ACT_NEW_USER_ID = 200 ActivityPeer.NEW_USER_ID.substring( 201 ActivityPeer.NEW_USER_ID.indexOf('.')+1); 202 private static final String ACTIVITYALIAS_NEW_USER_ID = 203 ACTIVITYALIAS + '.' + ACT_NEW_USER_ID; 204 205 private static final String WHERE = " WHERE "; 206 private static final String FROM = " FROM "; 207 private static final String ORDER_BY = " ORDER BY "; 208 private static final String BASE_OPTION_SORT_LEFT_JOIN = 209 " LEFT OUTER JOIN " + RModuleOptionPeer.TABLE_NAME + " sortRMO ON " + 210 '(' + IssuePeer.MODULE_ID + "=sortRMO.MODULE_ID AND " + 211 IssuePeer.TYPE_ID + "=sortRMO.ISSUE_TYPE_ID AND sortRMO.OPTION_ID="; 212 private static final String AV = "av"; 213 private static final String DOT_OPTION_ID_PAREN = ".OPTION_ID)"; 214 private static final String DOT_VALUE = ".VALUE"; 215 private static final String SORTRMO_PREFERRED_ORDER = 216 "sortRMO.PREFERRED_ORDER"; 217 218 219 private static final Integer NUMBERKEY_0 = new Integer (0); 220 221 227 private Connection conn; 228 229 233 private Statement searchStmt; 234 237 private ResultSet searchRS; 238 239 246 private List stmtList; 247 252 private List rsList; 253 254 257 private long connectionStartTime; 258 259 private SimpleDateFormat formatter; 260 261 private String searchWords; 262 private String commentQuery; 263 private Integer [] textScope; 264 private String minId; 265 private String maxId; 266 private String minDate; 267 private String maxDate; 268 private int minVotes; 269 270 private Integer stateChangeAttributeId; 271 private Integer stateChangeFromOptionId; 272 private Integer stateChangeToOptionId; 273 private String stateChangeFromDate; 274 private String stateChangeToDate; 275 276 private Integer sortAttributeId; 277 private String sortPolarity; 278 private MITList mitList; 279 280 private List userIdList; 281 private List userSearchCriteriaList; 282 private List lastUsedAVList; 283 private boolean modified; 284 285 private int lastTotalIssueCount = -1; 286 private List lastMatchingIssueIds = null; 287 private IteratorWithSize lastQueryResults = null; 288 289 private List issueListAttributeColumns; 291 292 private LRUMap moduleMap = new LRUMap(20); 295 private LRUMap rmitMap = new LRUMap(20); 296 297 private boolean isSearchAllowed = true; 298 299 300 private int joinCounter; 301 302 313 private Locale locale = Locale.US; 314 315 private StringValueParser parser = null; 316 317 private ScarabLocalizationTool L10N = null; 318 319 IssueSearch(Issue issue, ScarabUser searcher) 320 throws Exception 321 { 322 this(issue.getModule(), issue.getIssueType(), searcher); 323 324 List issueAttributes = issue.getAttributeValues(); 336 List searchAttributes = this.getAttributeValues(); 337 338 for (Iterator iter = issueAttributes.iterator(); iter.hasNext(); ) { 339 AttributeValue value = (AttributeValue) iter.next(); 340 searchAttributes.add(value.copy()); 341 } 342 } 343 344 IssueSearch(Module module, IssueType issueType, ScarabUser searcher) 345 throws Exception 346 { 347 super(module, issueType); 348 isSearchAllowed = 349 searcher.hasPermission(ScarabSecurity.ISSUE__SEARCH, module); 350 } 351 352 IssueSearch(MITList mitList, ScarabUser searcher) 353 throws Exception 354 { 355 super(); 356 if (mitList == null || mitList.size() == 0) 357 { 358 throw new IllegalArgumentException ("A non-null list with at" + 359 " least one item is required."); } 361 362 String [] perms = {ScarabSecurity.ISSUE__SEARCH}; 363 MITList searchableList = mitList 364 .getPermittedSublist(perms, searcher); 365 366 isSearchAllowed = searchableList.size() > 0; 367 isSearchAllowed=true; 368 369 if (searchableList.isSingleModuleIssueType()) 370 { 371 MITListItem item = searchableList.getFirstItem(); 372 setModuleId(item.getModuleId()); 373 setTypeId(item.getIssueTypeId()); 374 } 375 else 376 { 377 this.mitList = searchableList; 378 if (searchableList.isSingleModule()) 379 { 380 setModule(searchableList.getModule()); 381 } 382 if (searchableList.isSingleIssueType()) 383 { 384 setIssueType(searchableList.getIssueType()); 385 } 386 } 387 } 388 389 public Locale getLocale() { 390 return this.locale; 391 } 392 393 public void setLocale(Locale newLocale) { 394 this.locale = newLocale; 395 } 396 397 public boolean isXMITSearch() 398 { 399 return mitList != null && !mitList.isSingleModuleIssueType(); 400 } 401 402 407 public void setIssueListAttributeColumns(List rmuas) 408 { 409 issueListAttributeColumns = rmuas; 412 } 413 414 public List getIssueListAttributeColumns() 415 { 416 return issueListAttributeColumns; 417 } 418 419 public List getUserIdList() 420 { 421 return userIdList; 422 } 423 424 public LinkedMap getCommonAttributeValuesMap() 425 throws Exception 426 { 427 LinkedMap result = null; 428 if (isXMITSearch()) 429 { 430 result = getMITAttributeValuesMap(); 431 } 432 else 433 { 434 result = super.getModuleAttributeValuesMap(false); 435 } 436 return result; 437 } 438 439 445 private LinkedMap getMITAttributeValuesMap() 446 throws Exception 447 { 448 LinkedMap result = null; 449 450 List attributes = mitList.getCommonAttributes(false); 451 Map siaValuesMap = getAttributeValuesMap(); 452 if (attributes != null) 453 { 454 result = new LinkedMap((int)(1.25*attributes.size() + 1)); 455 Iterator i = attributes.iterator(); 456 while (i.hasNext()) 457 { 458 Attribute attribute = (Attribute)i.next(); 459 String key = attribute.getName().toUpperCase(); 460 if (siaValuesMap.containsKey(key)) 461 { 462 result.put(key, siaValuesMap.get(key)); 463 } 464 else 465 { 466 AttributeValue aval = AttributeValue 467 .getNewInstance(attribute, this); 468 addAttributeValue(aval); 469 result.put(key, aval); 470 } 471 } 472 } 473 return result; 474 } 475 476 480 public List getUserAttributes() 481 throws Exception 482 { 483 List result = null; 484 if (isXMITSearch()) 485 { 486 result = mitList.getCommonUserAttributes(false); 487 } 488 else 489 { 490 result = getModule().getUserAttributes(getIssueType(), false); 491 } 492 return result; 493 } 494 495 public List getLeafRModuleOptions(Attribute attribute) 496 throws Exception 497 { 498 List result = null; 499 if (isXMITSearch()) 500 { 501 result = mitList.getCommonLeafRModuleOptions(attribute); 502 } 503 else 504 { 505 result = getModule() 506 .getLeafRModuleOptions(attribute, getIssueType()); 507 } 508 return result; 509 } 510 511 public List getCommonOptionTree(Attribute attribute) 512 throws Exception 513 { 514 return mitList.getCommonRModuleOptionTree(attribute); 515 } 516 517 public List getAllOptionTree(Attribute attribute) 518 throws Exception 519 { 520 return mitList.getAllRModuleOptionTree(attribute); 521 } 522 523 528 public String getSearchWords() 529 { 530 return searchWords; 531 } 532 533 538 public void setSearchWords(String v) 539 { 540 if (!ObjectUtils.equals(v, this.searchWords)) 541 { 542 modified = true; 543 this.searchWords = v; 544 } 545 } 546 547 548 552 public String getCommentQuery() 553 { 554 return commentQuery; 555 } 556 557 561 public void setCommentQuery(String v) 562 { 563 if (!ObjectUtils.equals(v, this.commentQuery)) 564 { 565 modified = true; 566 this.commentQuery = v; 567 } 568 } 569 570 576 public Integer [] getTextScope() 577 throws Exception 578 { 579 if (textScope == null) 580 { 581 textScope = getTextScopeForAll(); 582 } 583 else 584 { 585 for (int i = textScope.length - 1; i >= 0; i--) 586 { 587 if (NUMBERKEY_0.equals(textScope[i])) 588 { 589 textScope = getTextScopeForAll(); 590 break; 591 } 592 } 593 } 594 return textScope; 595 } 596 597 598 601 private Integer [] getTextScopeForAll() 602 throws Exception 603 { 604 Integer [] textScope = null; 605 List textAttributes = getQuickSearchTextAttributeValues(); 606 if (textAttributes != null) 607 { 608 textScope = new Integer [textAttributes.size()]; 609 for (int j=textAttributes.size()-1; j>=0; j--) 610 { 611 textScope[j] = ((AttributeValue) 612 textAttributes.get(j)).getAttributeId(); 613 } 614 } 615 return textScope; 616 } 617 618 622 public void setTextScope(Integer [] v) 623 throws Exception 624 { 625 if (v != null) 626 { 627 for (int i=v.length-1; i>=0; i--) 628 { 629 if (v[i].equals(NUMBERKEY_0)) 630 { 631 v = getTextScopeForAll(); 632 break; 633 } 634 } 635 } 636 637 if (v == null) 640 { 641 modified |= this.textScope != null; 642 this.textScope = null; 643 } 644 else if (this.textScope != null && this.textScope.length == v.length) 645 { 646 for (int i=v.length-1; i>=0; i--) 647 { 648 if (!v[i].equals(this.textScope[i])) 649 { 650 modified = true; 651 this.textScope = v; 652 break; 653 } 654 } 655 } 656 else 657 { 658 modified = true; 659 this.textScope = v; 660 } 661 } 662 663 664 668 public String getMinId() 669 { 670 return minId; 671 } 672 673 677 public void setMinId(String v) 678 { 679 if (v != null && v.length() == 0) 680 { 681 v = null; 682 } 683 if (!ObjectUtils.equals(v, this.minId)) 684 { 685 modified = true; 686 this.minId = v; 687 } 688 } 689 690 691 695 public String getMaxId() 696 { 697 return maxId; 698 } 699 700 704 public void setMaxId(String v) 705 { 706 if (v != null && v.length() == 0) 707 { 708 v = null; 709 } 710 if (!ObjectUtils.equals(v, this.maxId)) 711 { 712 modified = true; 713 this.maxId = v; 714 } 715 } 716 717 718 722 public String getMinDate() 723 { 724 return this.minDate; 725 } 726 727 731 public void setMinDate(String newMinDate) 732 { 733 if (newMinDate != null && newMinDate.length() == 0) 734 { 735 newMinDate = null; 736 } 737 738 if (!ObjectUtils.equals(newMinDate, this.minDate)) 739 { 740 this.modified = true; 741 this.minDate = newMinDate; 742 } 743 } 744 745 746 750 public String getMaxDate() 751 { 752 return this.maxDate; 753 } 754 755 759 public void setMaxDate(String newMaxDate) 760 { 761 if (newMaxDate != null && newMaxDate.length() == 0) 762 { 763 newMaxDate = null; 764 } 765 766 if (!ObjectUtils.equals(newMaxDate, this.maxDate)) 767 { 768 this.modified = true; 769 this.maxDate = newMaxDate; 770 } 771 } 772 773 777 public int getMinVotes() 778 { 779 return minVotes; 780 } 781 782 786 public void setMinVotes(int v) 787 { 788 if (v != this.minVotes) 789 { 790 modified = true; 791 this.minVotes = v; 792 } 793 } 794 795 796 800 public Integer getStateChangeAttributeId() 801 { 802 return stateChangeAttributeId; 803 } 804 805 809 public void setStateChangeAttributeId(Integer v) 810 { 811 if (!ObjectUtils.equals(v, this.stateChangeAttributeId)) 812 { 813 modified = true; 814 this.stateChangeAttributeId = v; 815 } 816 } 817 818 822 public Integer getStateChangeFromOptionId() 823 { 824 return stateChangeFromOptionId; 825 } 826 827 831 public void setStateChangeFromOptionId(Integer v) 832 { 833 if (!ObjectUtils.equals(v, this.stateChangeFromOptionId)) 834 { 835 modified = true; 836 this.stateChangeFromOptionId = v; 837 } 838 } 839 840 844 public Integer getStateChangeToOptionId() 845 { 846 return stateChangeToOptionId; 847 } 848 849 853 public void setStateChangeToOptionId(Integer v) 854 { 855 if (!ObjectUtils.equals(v, this.stateChangeToOptionId)) 856 { 857 modified = true; 858 this.stateChangeToOptionId = v; 859 } 860 } 861 862 863 867 public String getStateChangeFromDate() 868 { 869 return this.stateChangeFromDate; 870 } 871 872 876 public void setStateChangeFromDate(String fromDate) 877 { 878 if (fromDate != null && fromDate.length() == 0) 879 { 880 fromDate = null; 881 } 882 883 if (!ObjectUtils.equals(fromDate, this.stateChangeFromDate)) 884 { 885 this.modified = true; 886 this.stateChangeFromDate = fromDate; 887 } 888 } 889 890 891 895 public String getStateChangeToDate() 896 { 897 return this.stateChangeToDate; 898 } 899 900 904 public void setStateChangeToDate(String toDate) 905 { 906 if (toDate != null && toDate.length() == 0) 907 { 908 toDate = null; 909 } 910 911 if (!ObjectUtils.equals(toDate, this.stateChangeToDate)) 912 { 913 this.modified = true; 914 this.stateChangeToDate = toDate; 915 } 916 } 917 918 919 923 public Integer getSortAttributeId() 924 { 925 return sortAttributeId; 926 } 927 928 932 public void setSortAttributeId(Integer v) 933 { 934 if (!ObjectUtils.equals(v, this.sortAttributeId)) 935 { 936 modified = true; 937 this.sortAttributeId = v; 938 } 939 } 940 941 946 public String getSortPolarity() 947 { 948 return (DESC.equals(sortPolarity) ? DESC : ASC); 949 } 950 951 955 public void setSortPolarity(String v) 956 { 957 if (!ObjectUtils.equals(v, this.sortPolarity)) 958 { 959 modified = true; 960 this.sortPolarity = v; 961 } 962 } 963 964 972 public void addUserCriteria(String userId, String searchCriteria) 973 { 974 if (userId == null) 975 { 976 throw new IllegalArgumentException ("userId cannot be null."); } 978 if (searchCriteria == null) 979 { 980 searchCriteria = ANY_KEY; 981 } 982 983 if (userIdList == null) 984 { 985 userIdList = new ArrayList (4); 986 userSearchCriteriaList = new ArrayList (4); 987 } 988 boolean newCriteria = true; 989 for (int i=userIdList.size()-1; i>=0 && newCriteria; i--) 990 { 991 Object attrId = userSearchCriteriaList.get(i); 992 newCriteria = !(userId.equals(userIdList.get(i)) && 995 (searchCriteria.equals(attrId) || ANY_KEY.equals(attrId))); 996 } 997 998 if (newCriteria) 999 { 1000 modified = true; 1001 if (ANY_KEY.equals(searchCriteria)) 1003 { 1004 for (int i=userIdList.size()-1; i>=0; i--) 1005 { 1006 if (userId.equals(userIdList.get(i))) 1007 { 1008 userIdList.remove(i); 1009 userSearchCriteriaList.remove(i); 1010 } 1011 } 1012 } 1013 userIdList.add(userId); 1014 userSearchCriteriaList.add(searchCriteria); 1015 } 1016 } 1017 1018 private boolean isAVListModified() 1019 throws TorqueException 1020 { 1021 boolean result = false; 1022 if (lastUsedAVList == null) 1023 { 1024 result = true; 1025 } 1026 else 1027 { 1028 List avList = getAttributeValues(); 1029 int max = avList.size(); 1030 if (lastUsedAVList.size() == max) 1031 { 1032 for (int i=0; i<max; i++) 1033 { 1034 AttributeValue a1 = (AttributeValue)avList.get(i); 1035 AttributeValue a2 = (AttributeValue)lastUsedAVList.get(i); 1036 if (!ObjectUtils.equals(a1.getOptionId(), a2.getOptionId()) 1037 || !ObjectUtils.equals(a1.getUserId(), a2.getUserId()) 1038 || !ObjectUtils.equals(a1.getValue(), a2.getValue())) 1040 { 1041 result = true; 1042 } 1043 } 1044 } 1045 else 1046 { 1047 result = true; 1048 } 1049 } 1050 return result; 1051 } 1052 1053 1058 private void checkModified() 1059 throws TorqueException 1060 { 1061 if (modified || isAVListModified()) 1062 { 1063 modified = false; 1064 lastTotalIssueCount = -1; 1065 lastMatchingIssueIds = null; 1066 lastQueryResults = null; 1067 } 1068 } 1069 1070 public Integer getALL_TEXT() 1071 { 1072 return NUMBERKEY_0; 1073 } 1074 1075 public List getQuickSearchTextAttributeValues() 1076 throws Exception 1077 { 1078 return getTextAttributeValues(true); 1079 } 1080 1081 public List getTextAttributeValues() 1082 throws Exception 1083 { 1084 return getTextAttributeValues(false); 1085 } 1086 1087 private List getTextAttributeValues(boolean quickSearchOnly) 1088 throws Exception 1089 { 1090 LinkedMap searchValues = getCommonAttributeValuesMap(); 1091 List searchAttributes = new ArrayList (searchValues.size()); 1092 1093 for (int i=0; i<searchValues.size(); i++) 1094 { 1095 AttributeValue searchValue = 1096 (AttributeValue)searchValues.getValue(i); 1097 if ((!quickSearchOnly || searchValue.isQuickSearchAttribute()) 1098 && searchValue.getAttribute().isTextAttribute()) 1099 { 1100 searchAttributes.add(searchValue); 1101 } 1102 } 1103 1104 return searchAttributes; 1105 } 1106 1107 1113 public List getQuickSearchOptionAttributeValues() 1114 throws Exception 1115 { 1116 return getOptionAttributeValues(true); 1117 } 1118 1119 1125 public List getOptionAttributeValues() 1126 throws Exception 1127 { 1128 return getOptionAttributeValues(false); 1129 } 1130 1131 1132 1138 private List getOptionAttributeValues(boolean quickSearchOnly) 1139 throws Exception 1140 { 1141 LinkedMap searchValues = getCommonAttributeValuesMap(); 1142 List searchAttributeValues = new ArrayList (searchValues.size()); 1143 1144 for (int i=0; i<searchValues.size(); i++) 1145 { 1146 AttributeValue searchValue = 1147 (AttributeValue)searchValues.getValue(i); 1148 if ((!quickSearchOnly || searchValue.isQuickSearchAttribute()) 1149 && searchValue instanceof OptionAttribute) 1150 { 1151 searchAttributeValues.add(searchValue); 1152 } 1153 } 1154 1155 return searchAttributeValues; 1156 } 1157 1158 1159 1164 private List removeUnsetValues(List attValues) 1165 { 1166 int size = attValues.size(); 1167 List setAVs = new ArrayList (size); 1168 for (int i=0; i<size; i++) 1169 { 1170 AttributeValue attVal = (AttributeValue) attValues.get(i); 1171 if (attVal.isSet()) 1172 { 1173 setAVs.add(attVal); 1174 } 1175 } 1176 return setAVs; 1177 } 1178 1179 1180 private void addAnd(StringBuffer sb) 1181 { 1182 if (sb.length() > 0) 1183 { 1184 sb.append(AND); 1185 } 1186 } 1187 1188 private void addIssueIdRange(StringBuffer where) 1189 throws ScarabException, Exception 1190 { 1191 if ((minId != null && minId.length() != 0) 1194 || (maxId != null && maxId.length() != 0)) 1195 { 1196 StringBuffer sb = new StringBuffer (); 1197 String domain = null; 1198 String prefix = null; 1199 Issue.FederatedId minFid = null; 1200 Issue.FederatedId maxFid = null; 1201 if (minId == null || minId.length() == 0) 1202 { 1203 maxFid = new Issue.FederatedId(maxId); 1204 setDefaults(null, maxFid); 1205 addAnd(sb); 1206 sb.append(IssuePeer.ID_COUNT).append("<=") 1207 .append(maxFid.getCount()); 1208 domain = maxFid.getDomain(); 1209 prefix = maxFid.getPrefix(); 1210 } 1211 else if (maxId == null || maxId.length() == 0) 1212 { 1213 minFid = new Issue.FederatedId(minId); 1214 setDefaults(minFid, null); 1215 addAnd(sb); 1216 sb.append(IssuePeer.ID_COUNT).append(">=") 1217 .append(minFid.getCount()); 1218 domain = minFid.getDomain(); 1219 prefix = minFid.getPrefix(); 1220 } 1221 else 1222 { 1223 minFid = new Issue.FederatedId(minId); 1224 maxFid = new Issue.FederatedId(maxId); 1225 setDefaults(minFid, maxFid); 1226 1227 if (minFid.getCount() <= maxFid.getCount() 1231 && StringUtils.equals(minFid.getPrefix(), maxFid.getPrefix()) 1232 && StringUtils.equals(minFid.getDomain(), maxFid.getDomain())) 1233 { 1234 addAnd(sb); 1235 sb.append(IssuePeer.ID_COUNT).append(">=") 1236 .append(minFid.getCount()).append(AND) 1237 .append(IssuePeer.ID_COUNT).append("<=") 1238 .append(maxFid.getCount()); 1239 domain = minFid.getDomain(); 1240 prefix = minFid.getPrefix(); 1241 } 1242 else 1243 { 1244 throw new ScarabException(L10NKeySet.ExceptionIncompatibleIssueIds, 1245 minId, 1246 maxId); 1247 } 1248 } 1249 if (domain != null) 1250 { 1251 sb.append(AND).append(IssuePeer.ID_DOMAIN).append("='") 1252 .append(domain).append('\''); 1253 } 1254 if (prefix != null) 1255 { 1256 sb.append(AND).append(IssuePeer.ID_PREFIX).append("='") 1257 .append(prefix).append('\''); 1258 } 1259 where.append(AND).append(sb); 1260 } 1261 } 1262 1263 1264 1267 private void setDefaults(FederatedId minFid, 1268 FederatedId maxFid) 1269 throws Exception 1270 { 1271 Module module = getModule(); 1272 if (module != null) 1273 { 1274 if (minFid != null && minFid.getDomain() == null) 1275 { 1276 minFid.setDomain(module.getScarabInstanceId()); 1277 } 1278 if (maxFid != null && maxFid.getDomain() == null) 1279 { 1280 maxFid.setDomain(module.getScarabInstanceId()); 1281 } 1282 if (minFid != null && minFid.getPrefix() == null) 1283 { 1284 minFid.setPrefix(module.getCode()); 1285 } 1286 } 1287 if (maxFid != null && maxFid.getPrefix() == null) 1288 { 1289 if (minFid == null) 1290 { 1291 maxFid.setPrefix(module.getCode()); 1292 } 1293 else 1294 { 1295 maxFid.setPrefix(minFid.getPrefix()); 1296 } 1297 } 1298 } 1299 1300 1314 public Date parseDate(String dateString, 1315 boolean addTwentyFourHours) 1316 throws ParseException 1317 { 1318 Date date = null; 1319 if (dateString != null) 1320 { 1321 if (dateString.indexOf(':') == -1) 1322 { 1323 String [] patterns = { 1329 Localization.getString(this.locale, "ShortDatePattern"), 1330 ScarabConstants.ISO_DATE_PATTERN }; 1331 date = parseDate(dateString, patterns); 1332 1333 if (date == null) 1335 { 1336 date = DateFormat.getDateInstance().parse(dateString); 1342 } 1343 1344 if (addTwentyFourHours) 1346 { 1347 date.setTime(date.getTime() + 86399999); 1348 } 1349 } 1350 else 1351 { 1352 String [] patterns = { 1358 Localization.getString(this.locale, "ShortDateTimePattern"), 1359 ScarabConstants.ISO_DATETIME_PATTERN }; 1360 date = parseDate(dateString, patterns); 1361 1362 if (date == null) 1364 { 1365 date = DateFormat.getDateTimeInstance().parse(dateString); 1366 } 1367 } 1368 } 1369 1370 return date; 1371 } 1372 1373 1387 private Date parseDate(String s, String [] patterns) 1388 throws ParseException 1389 { 1390 1393 if (s == null) 1394 { 1395 throw new ParseException ("Input string was null", -1); } 1397 1398 if (formatter == null) 1399 { 1400 formatter = new SimpleDateFormat (); 1401 } 1402 1403 for (int i = 0; i < patterns.length; i++) 1404 { 1405 formatter.applyPattern(patterns[i]); 1406 Date date = parseDateWithFormat(s, formatter); 1407 1408 if (date != null) 1409 { 1410 return date; 1411 } 1412 } 1413 1414 throw new ParseException ("Date could not be parsed with any" 1415 + " of the provided date patterns.", -1); } 1417 1418 private Date parseDateWithFormat(String dateString, DateFormat format) { 1419 try 1420 { 1421 return format.parse(dateString); 1422 } 1423 catch (ParseException ex) 1424 { 1425 return null; 1426 } 1427 } 1428 1429 1430 private void addDateRange(String column, Date minUtilDate, 1431 Date maxUtilDate, StringBuffer sb) 1432 throws Exception 1433 { 1434 if (minUtilDate != null || maxUtilDate != null) 1437 { 1438 DB adapter = Torque.getDB(Torque.getDefaultDB()); 1439 if (minUtilDate == null) 1440 { 1441 sb.append(column).append('<') 1442 .append(adapter.getDateString(maxUtilDate)); 1443 } 1444 else if (maxUtilDate == null) 1445 { 1446 sb.append(column).append(">=") 1447 .append(adapter.getDateString(minUtilDate)); 1448 } 1449 else 1450 { 1451 if (minUtilDate.before(maxUtilDate)) 1455 { 1456 sb.append(column).append(">=") 1457 .append(adapter.getDateString(minUtilDate)); 1458 sb.append(AND); 1459 sb.append(column).append('<') 1460 .append(adapter.getDateString(maxUtilDate)); 1461 } 1462 else 1463 { 1464 throw new ScarabException(L10NKeySet.ExceptionMaxdateBeforeMindate, 1465 this.maxDate, 1466 minUtilDate); 1467 } 1468 } 1469 } 1470 } 1471 1472 1473 1479 private void addSelectedAttributes(StringBuffer fromClause, 1480 List attValues, Set tableAliases) 1481 throws Exception 1482 { 1483 Map attrMap = new HashMap ((int)(attValues.size()*1.25)); 1484 for (int j=0; j<attValues.size(); j++) 1485 { 1486 AttributeValue multiAV = (AttributeValue)attValues.get(j); 1487 if (multiAV instanceof OptionAttribute) 1488 { 1489 Integer index = multiAV.getAttributeId(); 1490 List options = (List )attrMap.get(index); 1491 if (options == null) 1492 { 1493 options = new ArrayList (); 1494 attrMap.put(index, options); 1495 } 1496 1497 List chainedValues = multiAV.getValueList(); 1499 for (int i=0; i<chainedValues.size(); i++) 1500 { 1501 AttributeValue aval = (AttributeValue)chainedValues.get(i); 1502 buildOptionList(options, aval); 1503 } 1504 } 1505 } 1506 1507 for (Iterator i=attrMap.entrySet().iterator(); i.hasNext();) 1508 { 1509 Map.Entry entry = (Map.Entry )i.next(); 1510 String alias = "av" + entry.getKey(); 1511 List options = (List )entry.getValue(); 1512 String c2 = null; 1513 if (options.size() == 1) 1514 { 1515 c2 = alias + '.' + AV_OPTION_ID + '=' 1516 + options.get(0); 1517 } 1518 else 1519 { 1520 c2 = alias + '.' + AV_OPTION_ID + " IN (" 1521 + StringUtils.join(options.iterator(), ",") + ')'; 1522 } 1523 joinCounter++; 1524 String joinClause = INNER_JOIN + AttributeValuePeer.TABLE_NAME 1525 + ' ' + alias + " ON (" + 1526 alias + '.' + AV_ISSUE_ID + '=' + IssuePeer.ISSUE_ID + 1527 AND + c2 + AND + 1528 alias + '.' + "DELETED=0" + ')'; 1529 fromClause.append(joinClause); 1532 tableAliases.add(alias); 1533 } 1534 } 1535 1536 1565 private void buildOptionList(List options, AttributeValue aval) 1566 throws Exception 1567 { 1568 List descendants = null; 1569 if (isXMITSearch()) 1576 { 1577 descendants = 1578 mitList.getDescendantsUnion(aval.getAttributeOption()); 1579 } 1580 else 1581 { 1582 IssueType issueType = getIssueType(); 1583 1584 RModuleOption rmo = getModule() 1590 .getRModuleOption(aval.getAttributeOption(), issueType); 1591 if (rmo != null) 1592 { 1593 descendants = rmo.getDescendants(issueType); 1594 } 1595 } 1596 1597 options.add(aval.getOptionId()); 1602 1603 if (descendants != null && !descendants.isEmpty()) 1604 { 1605 for (Iterator i = descendants.iterator(); i.hasNext();) 1609 { 1610 options.add(((RModuleOption)i.next()) 1611 .getOptionId()); 1612 } 1613 } 1614 } 1615 1616 private void addUserAndCreatedDateCriteria(StringBuffer from, 1617 StringBuffer where) 1618 throws Exception 1619 { 1620 String dateRangeSql = null; 1621 if (getMinDate() != null || getMaxDate() != null) 1622 { 1623 StringBuffer sbdate = new StringBuffer (); 1624 Date minUtilDate = parseDate(getMinDate(), false); 1625 Date maxUtilDate = parseDate(getMaxDate(), true); 1626 addDateRange(ACTIVITYSETALIAS + '.' + CREATED_DATE, 1627 minUtilDate, maxUtilDate, sbdate); 1628 dateRangeSql = sbdate.toString(); 1629 } 1630 1631 if (userIdList == null || userIdList.isEmpty()) 1632 { 1633 if (dateRangeSql != null) 1634 { 1635 joinCounter++; 1636 from.append(INNER_JOIN).append(ActivitySetPeer.TABLE_NAME) 1638 .append(' ').append(ACTIVITYSETALIAS).append(ON).append( 1639 ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID) 1640 .append(AND).append(dateRangeSql) 1641 .append(')'); 1642 } 1643 } 1644 else 1645 { 1646 List anyUsers = null; 1647 List creatorUsers = null; 1648 Map attrUsers = null; 1649 1650 int maxUsers = userIdList.size(); 1651 for (int i =0; i<maxUsers; i++) 1653 { 1654 String userId = (String )userIdList.get(i); 1655 String attrId = (String )userSearchCriteriaList.get(i); 1656 if (attrId == null || ANY_KEY.equals(attrId)) 1657 { 1658 if (anyUsers == null) 1659 { 1660 anyUsers = new ArrayList (maxUsers); 1661 } 1662 anyUsers.add(userId); 1663 } 1664 else if (CREATED_BY_KEY.equals(attrId)) 1665 { 1666 if (creatorUsers == null) 1667 { 1668 creatorUsers = new ArrayList (maxUsers); 1669 } 1670 creatorUsers.add(userId); 1671 } 1672 else 1673 { 1674 if (attrUsers == null) 1677 { 1678 attrUsers = new HashMap (maxUsers); 1679 } 1680 List userIds = (List )attrUsers.get(attrId); 1681 if (userIds == null) 1682 { 1683 userIds = new ArrayList (maxUsers); 1684 attrUsers.put(attrId, userIds); 1685 } 1686 userIds.add(userId); 1687 } 1688 } 1689 1690 joinCounter++; 1693 StringBuffer fromClause = new StringBuffer (100); 1694 fromClause.append(INNER_JOIN).append(ActivityPeer.TABLE_NAME) 1695 .append(' ').append(ACTIVITYALIAS).append(ON) 1696 .append(ACTIVITYALIAS_ISSUE_ID__EQUALS__ISSUEPEER_ISSUE_ID); 1697 1698 StringBuffer attrCrit = null; 1699 if (anyUsers != null) 1700 { 1701 attrCrit = new StringBuffer (50); 1702 attrCrit.append('('); 1703 addUserActivityFragment(attrCrit, anyUsers); 1704 attrCrit.append(')'); 1705 } 1706 1707 if (attrUsers != null) 1711 { 1712 for (Iterator i = attrUsers.entrySet().iterator(); i.hasNext();) 1713 { 1714 if (attrCrit == null) 1715 { 1716 attrCrit = new StringBuffer (); 1717 } 1718 else 1719 { 1720 attrCrit.append(OR); 1721 } 1722 1723 Map.Entry entry = (Map.Entry )i.next(); 1724 String attrId = (String )entry.getKey(); 1725 List userIds = (List )entry.getValue(); 1726 attrCrit.append('('); 1727 addUserActivityFragment(attrCrit, userIds); 1728 attrCrit.append(AND + 1729 ACTIVITYALIAS + '.' + ATTRIBUTE_ID + '=' + attrId); 1730 attrCrit.append(')'); 1731 } 1732 } 1733 1734 boolean isAddActivitySet = anyUsers != null || creatorUsers != null 1735 || dateRangeSql != null; 1736 String whereClause = null; 1737 if (isAddActivitySet) 1738 { 1739 if (attrCrit != null) 1740 { 1741 whereClause = '(' + attrCrit.toString() + ')'; 1742 } 1743 1744 joinCounter++; 1745 fromClause.append(')').append(INNER_JOIN) 1746 .append(ActivitySetPeer.TABLE_NAME) 1747 .append(' ').append(ACTIVITYSETALIAS).append(ON).append( 1748 ISSUEPEER_TRAN_ID__EQUALS__ACTIVITYSETALIAS_TRAN_ID); 1749 1750 if (anyUsers != null || creatorUsers != null) 1751 { 1752 List anyAndCreators = new ArrayList (maxUsers); 1753 if (anyUsers != null) 1754 { 1755 anyAndCreators.addAll(anyUsers); 1756 } 1757 if (creatorUsers != null) 1758 { 1759 anyAndCreators.addAll(creatorUsers); 1760 } 1761 1762 String createdBySqlFragment = 1765 ACTIVITYSETALIAS + '.' + CREATED_BY; 1766 if (anyAndCreators.size() == 1) 1767 { 1768 createdBySqlFragment += 1769 '=' + anyAndCreators.get(0).toString(); 1770 } 1771 else 1772 { 1773 createdBySqlFragment += IN + 1774 StringUtils.join(anyAndCreators.iterator(), ",") 1775 + ')'; 1776 } 1777 1778 if (anyUsers != null || attrUsers != null) 1779 { 1780 fromClause.append(')'); 1781 whereClause = '(' + whereClause + OR + 1782 createdBySqlFragment + ')'; 1783 if (dateRangeSql != null) 1784 { 1785 whereClause += AND + dateRangeSql; 1786 } 1787 } 1788 else 1789 { 1790 fromClause.append(AND).append(createdBySqlFragment); 1791 if (dateRangeSql != null) 1792 { 1793 fromClause.append(AND).append(dateRangeSql); 1794 } 1795 fromClause.append(')'); 1796 } 1797 } 1798 else { 1800 fromClause.append(AND).append(dateRangeSql).append(')'); 1801 } 1802 } 1803 else 1804 { 1805 fromClause.append(AND).append('(').append(attrCrit) 1809 .append("))"); 1810 } 1811 1812 from.append(fromClause.toString()); 1813 if (whereClause != null) 1814 { 1815 where.append(AND).append(whereClause); 1816 } 1817 } 1818 } 1819 1820 private void addUserActivityFragment(StringBuffer sb, List userIds) 1821 { 1822 sb.append(ACTIVITYALIAS + '.' + END_DATE + 1823 IS_NULL + AND + ACTIVITYALIAS_NEW_USER_ID); 1824 if (userIds.size() == 1) 1825 { 1826 sb.append('=').append(userIds.get(0)); 1827 } 1828 else 1829 { 1830 sb.append(IN + 1831 StringUtils.join(userIds.iterator(), ",") + ')'); 1832 } 1833 } 1834 1835 1836 private Long [] getTextMatches(List attValues, boolean mergeTextResults) 1837 throws Exception 1838 { 1839 boolean searchCriteriaExists = false; 1840 Long [] matchingIssueIds = null; 1841 SearchIndex searchIndex = SearchFactory.getInstance(); 1842 if (searchIndex == null) 1843 { 1844 throw new Exception ("No index available to search"); } 1847 if (getSearchWords() != null && getSearchWords().length() != 0) 1848 { 1849 searchIndex.addQuery(getTextScope(), getSearchWords()); 1850 searchCriteriaExists = true; 1851 } 1852 else 1853 { 1854 for (int i=0; i<attValues.size(); i++) 1855 { 1856 AttributeValue aval = (AttributeValue)attValues.get(i); 1857 if (aval.getValue() != null 1858 && aval.getValue().length() != 0) 1859 { 1860 1861 if (parser != null && aval instanceof DateAttribute) 1862 { 1863 String auxDate = parser.getString("attv__" + aval.getAttributeId().intValue() + "val_aux"); 1864 if (auxDate == null) 1865 auxDate = ""; 1866 else 1867 auxDate = DateAttribute.internalDateFormat(auxDate.trim(), L10N.get(L10NKeySet.ShortDatePattern)); 1868 aval.setValue(DateAttribute.internalDateFormat(aval.getValue(), L10N.get(L10NKeySet.ShortDatePattern))); 1869 searchCriteriaExists = true; 1870 Integer [] id = {aval.getAttributeId()}; 1871 if (auxDate.equals(aval.getValue())) 1872 searchIndex.addQuery(id, aval.getValue()); 1873 else 1874 searchIndex.addQuery(id, SearchIndex.TEXT + ":["+aval.getValue() + " TO " + auxDate + "]"); 1875 } 1876 else if (aval instanceof StringAttribute) 1877 { 1878 searchCriteriaExists = true; 1879 Integer [] id = {aval.getAttributeId()}; 1880 searchIndex 1881 .addQuery(id, aval.getValue()); 1882 } 1883 } 1884 1885 } 1886 } 1887 1888 String commentQuery = getCommentQuery(); 1890 if (commentQuery != null && commentQuery.trim().length() > 0) 1891 { 1892 Integer [] id = {AttachmentTypePeer.COMMENT_PK}; 1893 searchIndex.addAttachmentQuery(id, commentQuery); 1894 searchCriteriaExists = true; 1895 } 1896 1897 if (searchCriteriaExists) 1898 { 1899 try 1900 { 1901 matchingIssueIds = searchIndex.getRelatedIssues(mergeTextResults); 1902 } 1903 catch (Exception e) 1904 { 1905 SearchFactory.releaseInstance(searchIndex); 1906 throw e; 1907 } 1908 } 1909 1910 SearchFactory.releaseInstance(searchIndex); 1911 return matchingIssueIds; 1912 1913 } 1914 1915 private void addStateChangeQuery(StringBuffer from) 1916 throws Exception 1917 { 1918 Integer oldOptionId = getStateChangeFromOptionId(); 1919 Integer newOptionId = getStateChangeToOptionId(); 1920 Date minUtilDate = parseDate(getStateChangeFromDate(), false); 1921 Date maxUtilDate = parseDate(getStateChangeToDate(), true); 1922 if ((oldOptionId != null && !oldOptionId.equals(NUMBERKEY_0)) 1923 || (newOptionId != null && !newOptionId.equals(NUMBERKEY_0)) 1924 || minUtilDate != null || maxUtilDate != null) 1925 { 1926 joinCounter++; 1927 from.append(INNER_JOIN + ActivityPeer.TABLE_NAME + ON + 1928 ActivityPeer.ISSUE_ID + '=' + IssuePeer.ISSUE_ID); 1929 1930 if (oldOptionId == null && newOptionId == null) 1931 { 1932 from.append(AND).append(ActivityPeer.ATTRIBUTE_ID) 1933 .append('=').append(getStateChangeAttributeId()); 1934 } 1935 else 1936 { 1937 if (newOptionId != null && !newOptionId.equals(NUMBERKEY_0)) 1938 { 1939 from.append(AND).append(ActivityPeer.NEW_OPTION_ID) 1940 .append('=').append(newOptionId); 1941 } 1942 if (oldOptionId != null && !oldOptionId.equals(NUMBERKEY_0)) 1943 { 1944 from.append(AND).append(ActivityPeer.OLD_OPTION_ID) 1945 .append('=').append(oldOptionId); 1946 } 1947 } 1948 from.append(')'); 1949 1950 if (minUtilDate != null || maxUtilDate != null) 1952 { 1953 joinCounter++; 1954 from.append(INNER_JOIN + ActivitySetPeer.TABLE_NAME + ON + 1955 ActivitySetPeer.TRANSACTION_ID + '=' + 1956 ActivityPeer.TRANSACTION_ID); 1957 from.append(AND); 1958 1959 addDateRange(ActivitySetPeer.CREATED_DATE, 1960 minUtilDate, maxUtilDate, from); 1961 1962 from.append(')'); 1963 } 1964 } 1965 } 1966 1967 private Long [] addCoreSearchCriteria(StringBuffer fromClause, 1968 StringBuffer whereClause, 1969 Set tableAliases, 1970 boolean mergePartialQueryResults) 1971 throws Exception 1972 { 1973 if (isXMITSearch()) 1974 { 1975 Criteria crit = new Criteria(); 1976 mitList.addToCriteria(crit); 1977 String sql = crit.toString(); 1978 int wherePos = sql.indexOf(" WHERE "); 1979 whereClause.append(sql.substring(wherePos + 7)); 1980 } 1981 else 1982 { 1983 whereClause.append(IssuePeer.MODULE_ID).append('=') 1984 .append(getModule().getModuleId()); 1985 whereClause.append(AND).append(IssuePeer.TYPE_ID).append('=') 1986 .append(getIssueType().getIssueTypeId()); 1987 } 1988 whereClause.append(AND).append(IssuePeer.DELETED).append("=0"); 1989 1990 lastUsedAVList = new ArrayList (getAttributeValues()); 1992 1993 List setAttValues = removeUnsetValues(lastUsedAVList); 1995 addSelectedAttributes(fromClause, setAttValues, tableAliases); 1996 1997 Long [] matchingIssueIds = getTextMatches(setAttValues, mergePartialQueryResults); 1999 2000 if (matchingIssueIds == null || matchingIssueIds.length > 0) 2001 { 2002 addIssueIdRange(whereClause); 2003 2005 addUserAndCreatedDateCriteria(fromClause, whereClause); 2007 2008 addIssuePKsCriteria(whereClause, matchingIssueIds); 2010 2011 addStateChangeQuery(fromClause); 2013 } 2014 return matchingIssueIds; 2015 } 2016 2017 private void addIssuePKsCriteria(StringBuffer sb, Long [] ids) 2018 { 2019 if (ids != null && ids.length > 0) 2020 { 2021 sb.append(AND).append(IssuePeer.ISSUE_ID).append(IN) 2022 .append(StringUtils.join(ids, ",")).append(')'); 2023 } 2024 } 2025 2026 2033 public IteratorWithSize getQueryResults() 2034 throws ComplexQueryException, Exception 2035 { 2036 return getQueryResults(false); 2037 } 2038 2039 2047 public IteratorWithSize getQueryResults(boolean mergePartialQueries) 2048 throws ComplexQueryException, Exception 2049 { 2050 checkModified(); 2051 if (!isSearchAllowed) 2052 { 2053 lastQueryResults = IteratorWithSize.EMPTY; 2054 } 2055 else if (lastQueryResults == null) 2056 { 2057 Set tableAliases = new HashSet (); 2058 StringBuffer from = new StringBuffer (); 2059 StringBuffer where = new StringBuffer (); 2060 joinCounter = 0; 2061 Long [] matchingIssueIds = addCoreSearchCriteria(from, where, 2062 tableAliases, 2063 mergePartialQueries); 2064 if (joinCounter > MAX_INNER_JOIN) 2065 { 2066 throw new ComplexQueryException(L10NKeySet.ExceptionQueryTooComplex); 2069 } 2070 if (matchingIssueIds == null || matchingIssueIds.length > 0) 2074 { 2075 lastQueryResults = getQueryResults(from, where, tableAliases, mergePartialQueries); 2076 } 2077 else 2078 { 2079 lastQueryResults = IteratorWithSize.EMPTY; 2080 } 2081 } 2082 2083 return lastQueryResults; 2084 } 2085 2086 public int getIssueCount() 2087 throws ComplexQueryException, Exception 2088 { 2089 2090 return getIssueCount(false); 2091 } 2092 2093 public int getIssueCount(boolean mergePartialQueries) 2094 throws ComplexQueryException, Exception 2095 { 2096 checkModified(); 2097 int count = 0; 2098 if (isSearchAllowed) 2099 { 2100 if (lastTotalIssueCount >= 0) 2101 { 2102 count = lastTotalIssueCount; 2103 } 2104 else 2105 { 2106 count = countFromDB(mergePartialQueries); 2107 } 2108 lastTotalIssueCount = count; 2109 } 2110 2111 return count; 2112 } 2113 2114 private int countFromDB(boolean mergePartialQueries) 2115 throws ComplexQueryException, Exception 2116 { 2117 int count = 0; 2118 StringBuffer from = new StringBuffer (); 2119 StringBuffer where = new StringBuffer (); 2120 joinCounter = 0; 2121 Long [] matchingIssueIds = addCoreSearchCriteria(from, where, 2122 new HashSet (), 2123 mergePartialQueries); 2124 if (joinCounter > MAX_INNER_JOIN) 2125 { 2126 throw new ComplexQueryException(L10NKeySet.ExceptionQueryTooComplex); 2129 } 2130 2131 if (matchingIssueIds == null || matchingIssueIds.length > 0) 2132 { 2133 StringBuffer sql = new StringBuffer ("SELECT count(DISTINCT "); 2134 sql.append(IssuePeer.ISSUE_ID).append(')').append(" FROM ") 2135 .append(IssuePeer.TABLE_NAME); 2136 if (from.length() > 0) 2137 { 2138 sql.append(' ').append(from.toString()); 2139 } 2140 if (where.length() > 0) 2141 { 2142 sql.append(WHERE).append(where.toString()); 2143 } 2144 String countSql = sql.toString(); 2145 2146 Connection localCon = conn; 2147 Statement stmt = null; 2148 try 2149 { 2150 if (localCon == null) 2151 { 2152 localCon = Torque.getConnection(); 2153 } 2154 long startTime = System.currentTimeMillis(); 2155 stmt = localCon.createStatement(); 2156 ResultSet resultSet = stmt.executeQuery(countSql); 2157 if (resultSet.next()) 2158 { 2159 count = resultSet.getInt(1); 2160 } 2161 logTime(countSql, System.currentTimeMillis() - startTime, 2162 50L, 500L); 2163 } 2164 finally 2165 { 2166 if (stmt != null) 2167 { 2168 stmt.close(); 2169 } 2170 if (conn == null && localCon != null) 2171 { 2172 localCon.close(); 2173 } 2174 } 2175 } 2176 return count; 2177 } 2178 2179 private static final String LOGGER = "org.apache.torque"; 2180 private void logTime(String message, long time, 2181 long infoLimit, long warnLimit) 2182 { 2183 if (time > warnLimit) 2184 { 2185 Log.get(LOGGER).warn(message + "\nTime = " + time + " ms"); 2186 } 2187 else if (time > infoLimit) 2188 { 2189 Logger log = Log.get(LOGGER); 2190 if (log.isInfoEnabled()) 2191 { 2192 log.info(message + "\nTime = " + time + " ms"); 2193 } 2194 } 2195 else 2196 { 2197 Logger log = Log.get(LOGGER); 2198 if (log.isDebugEnabled()) 2199 { 2200 log.info(message + "\nTime = " + time + " ms"); 2201 } 2202 } 2203 } 2204 2205 private String setupSortColumn(Integer sortAttrId, 2206 StringBuffer sortOuterJoin, 2207 Set tableAliases) 2208 throws TorqueException 2209 { 2210 String alias = AV + sortAttrId; 2211 if (!tableAliases.contains(alias)) 2212 { 2213 sortOuterJoin.append(LEFT_OUTER_JOIN) 2214 .append(AttributeValuePeer.TABLE_NAME).append(' ') 2215 .append(alias).append(ON) 2216 .append(IssuePeer.ISSUE_ID).append('=') 2217 .append(alias).append(".ISSUE_ID AND ").append(alias) 2218 .append(".DELETED=0 AND ").append(alias) 2219 .append(".ATTRIBUTE_ID=").append(sortAttrId).append(')'); 2220 } 2221 String sortColumn; 2222 Attribute att = AttributeManager.getInstance(sortAttrId); 2223 if (att.isOptionAttribute()) 2224 { 2225 sortColumn = SORTRMO_PREFERRED_ORDER; 2227 sortOuterJoin.append(BASE_OPTION_SORT_LEFT_JOIN).append(alias) 2229 .append(DOT_OPTION_ID_PAREN); 2230 } 2231 else 2232 { 2233 sortColumn = alias + DOT_VALUE; 2234 } 2235 return sortColumn; 2236 } 2237 2238 private List getSearchSqlPieces(StringBuffer from, StringBuffer where, 2239 Set tableAliases) 2240 throws TorqueException 2241 { 2242 List searchStuff = new ArrayList (3); 2243 Integer sortAttrId = getSortAttributeId(); 2244 2245 StringBuffer sql = getSelectStart(); 2247 sql.append(',').append(IssuePeer.MODULE_ID) 2248 .append(',').append(IssuePeer.TYPE_ID); 2249 String sortColumn = null; 2250 StringBuffer sortOuterJoin = null; 2251 if (sortAttrId != null) 2252 { 2253 sortOuterJoin = new StringBuffer (128); 2254 sortColumn = setupSortColumn(sortAttrId, sortOuterJoin, 2255 tableAliases); 2256 sql.append(',').append(sortColumn); 2257 } 2258 2259 sql.append(FROM).append(IssuePeer.TABLE_NAME); 2260 if (from.length() > 0) 2261 { 2262 sql.append(' ').append(from.toString()); 2263 } 2264 if (sortOuterJoin != null) 2265 { 2266 sql.append(sortOuterJoin); 2267 } 2268 if (where.length() > 0) 2269 { 2270 sql.append(WHERE).append(where.toString()); 2271 } 2272 addOrderByClause(sql, sortColumn); 2273 searchStuff.add(sql.toString()); 2274 2275 List rmuas = getIssueListAttributeColumns(); 2280 if (rmuas != null) 2281 { 2282 int valueListSize = rmuas.size(); 2283 StringBuffer outerJoin = new StringBuffer (10 * valueListSize + 20); 2284 2285 int count = 0; 2286 int maxJoin = MAX_JOIN - 2; 2287 StringBuffer partialSql = getSelectStart(); 2289 tableAliases = new HashSet (MAX_JOIN); 2290 for (Iterator i = rmuas.iterator(); i.hasNext();) 2291 { 2292 RModuleUserAttribute rmua = (RModuleUserAttribute)i.next(); 2293 Integer attrPK = rmua.getAttributeId(); 2294 2295 2296 String id = attrPK.toString(); 2297 String alias = AV + id; 2298 partialSql.append(',').append(alias).append(DOT_VALUE); 2300 if (!tableAliases.contains(alias) 2303 && !attrPK.equals(sortAttrId)) 2304 { 2305 outerJoin.append(LEFT_OUTER_JOIN) 2306 .append(AttributeValuePeer.TABLE_NAME).append(' ') 2307 .append(alias).append(ON) 2308 .append(IssuePeer.ISSUE_ID).append('=') 2309 .append(alias).append(".ISSUE_ID AND ").append(alias) 2310 .append(".DELETED=0 AND ").append(alias) 2311 .append(".ATTRIBUTE_ID=").append(id).append(')'); 2312 tableAliases.add(alias); 2313 } 2314 2315 count++; 2316 if (count == maxJoin || !i.hasNext()) 2317 { 2318 ColumnBundle cb = new ColumnBundle(); 2319 cb.size = count; 2320 if (sortAttrId != null) 2321 { 2322 cb.sortColumn = setupSortColumn(sortAttrId, outerJoin, 2323 tableAliases); 2324 partialSql.append(',').append( 2325 cb.sortColumn); 2326 } 2327 cb.select = partialSql; 2328 cb.outerJoins = outerJoin; 2329 searchStuff.add(cb); 2330 2331 partialSql = getSelectStart(); 2332 outerJoin = new StringBuffer (512); 2333 tableAliases.clear(); 2334 count = 0; 2335 } 2336 } 2337 } 2338 return searchStuff; 2339 } 2340 2341 private IteratorWithSize getQueryResults(StringBuffer from, 2342 StringBuffer where, 2343 Set tableAliases, 2344 boolean mergePartialQueries) 2345 throws TorqueException, ComplexQueryException, Exception 2346 { 2347 IteratorWithSize result = null; 2349 try 2350 { 2351 if (conn == null) 2352 { 2353 conn = Torque.getConnection(); 2354 connectionStartTime = System.currentTimeMillis(); 2355 } 2356 2357 int count = getIssueCount(mergePartialQueries); 2363 if (count > 0) 2364 { 2365 result = new QueryResultIterator(this, count, from, where, 2366 tableAliases); 2367 } 2368 else 2369 { 2370 result = IteratorWithSize.EMPTY; 2371 } 2372 } 2373 catch (SQLException e) 2374 { 2375 close(); 2376 throw e; } 2378 2385 return result; 2386 } 2387 2388 private void addOrderByClause(StringBuffer sql, String sortColumn) 2389 { 2390 if (sortColumn == null) 2391 { 2392 sql.append(ORDER_BY).append(IssuePeer.ID_PREFIX); 2393 sql.append(' ').append(getSortPolarity()); 2394 sql.append(',').append(IssuePeer.ID_COUNT); 2395 sql.append(' ').append(getSortPolarity()); 2396 } 2397 else 2398 { 2399 sql.append(ORDER_BY).append(sortColumn); 2400 sql.append(' ').append(getSortPolarity()); 2401 sql.append(',').append(IssuePeer.ISSUE_ID).append(" ASC"); 2403 } 2404 } 2405 2406 private StringBuffer getSelectStart() 2407 { 2408 StringBuffer sql = new StringBuffer (512); 2409 sql.append(SELECT_DISTINCT) 2410 .append(IssuePeer.ISSUE_ID).append(',') 2411 .append(IssuePeer.ID_PREFIX).append(',') 2412 .append(IssuePeer.ID_COUNT); 2413 return sql; 2414 } 2415 2416 2417 2428 Module getModule(Integer id) 2429 throws TorqueException 2430 { 2431 Module module = (Module)moduleMap.get(id); 2432 if (module == null) 2433 { 2434 module = ModuleManager.getInstance(id); 2435 moduleMap.put(id, module); 2436 } 2437 return module; 2438 } 2439 2440 2452 RModuleIssueType getRModuleIssueType(Integer moduleId, Integer issueTypeId) 2453 throws TorqueException 2454 { 2455 SimpleKey[] nks = {SimpleKey.keyFor(moduleId.intValue()), 2456 SimpleKey.keyFor(issueTypeId.intValue())}; 2457 ObjectKey key = new ComboKey(nks); 2458 RModuleIssueType rmit = (RModuleIssueType)rmitMap.get(key); 2459 if (rmit == null) 2460 { 2461 rmit = RModuleIssueTypeManager.getInstance(key); 2462 rmitMap.put(key, rmit); 2463 } 2464 return rmit; 2465 } 2466 2467 2473 protected void finalize() 2474 throws Throwable 2475 { 2476 try 2477 { 2478 if (conn != null) 2479 { 2480 Log.get(LOGGER) 2481 .warn("Closing connection in " + this + " finalizer"); 2482 IssueSearchFactory.INSTANCE.notifyDone(); 2487 } 2488 } 2489 finally 2490 { 2491 close(); 2492 super.finalize(); 2493 } 2494 } 2495 2496 2500 public void close() 2501 { 2502 if (conn != null) 2503 { 2504 try 2507 { 2508 Logger log = Log.get(LOGGER); 2509 if (log.isDebugEnabled()) 2510 { 2511 log.debug("Releasing issue search database connection"); 2512 } 2513 } 2514 finally 2515 { 2516 try 2517 { 2518 if (searchRS != null) 2519 { 2520 searchRS.close(); 2521 searchRS = null; 2522 } 2523 } 2524 catch (Exception e) 2525 { 2526 try 2527 { 2528 Log.get(LOGGER).warn( 2529 "Unable to close jdbc Statement", e); 2530 } 2531 catch (Exception ignore) 2532 { 2533 } 2534 } 2535 try 2536 { 2537 if (searchStmt != null) 2538 { 2539 searchStmt.close(); 2540 searchStmt = null; 2541 } 2542 } 2543 catch (Exception e) 2544 { 2545 try 2546 { 2547 Log.get(LOGGER).warn( 2548 "Unable to close jdbc Statement", e); 2549 } 2550 catch (Exception ignore) 2551 { 2552 } 2553 } 2554 2555 try 2556 { 2557 closeStatementsAndResultSets(); 2558 stmtList = null; 2559 rsList = null; 2560 } 2561 catch (Exception e) 2562 { 2563 try 2564 { 2565 Log.get(LOGGER).warn( 2566 "Unable to close jdbc Statement", e); 2567 } 2568 catch (Exception ignore) 2569 { 2570 } 2571 } 2572 2573 Torque.closeConnection(this.conn); 2574 this.conn = null; 2575 logTime(this + 2576 " released database connection which was held for:", 2577 System.currentTimeMillis() - connectionStartTime, 2578 5000L, 30000L); 2579 } 2580 } 2581 } 2582 2583 2584 private void closeStatementsAndResultSets() 2585 throws SQLException 2586 { 2587 if (rsList != null) 2588 { 2589 for (Iterator iter = rsList.iterator(); iter.hasNext();) 2590 { 2591 ((ResultSetAndSize)iter.next()).resultSet.close(); 2592 } 2593 rsList.clear(); 2594 } 2595 2596 if (stmtList != null) 2597 { 2598 for (Iterator iter = stmtList.iterator(); iter.hasNext();) 2599 { 2600 ((Statement )iter.next()).close(); 2601 } 2602 stmtList.clear(); 2603 } 2604 } 2605 2606 2610 public void setQuery(String query) throws Exception 2611 { 2612 this.parser = new StringValueParser(); 2613 parser.parse(query, '&', '=', true); 2614 } 2615 2616 2621 public void setLocalizationTool(ScarabLocalizationTool l10nTool) 2622 { 2623 this.L10N = l10nTool; 2624 } 2625 2626 private class QueryResultIterator implements IteratorWithSize 2627 { 2628 final IssueSearch search; 2629 final List searchStuff; 2630 final int size; 2631 2632 QueryResult[] cachedQRs; 2633 2634 2637 private QueryResultIterator(IssueSearch search, int size, 2638 StringBuffer from, StringBuffer where, 2639 Set tableAliases) 2640 throws SQLException , TorqueException 2641 { 2642 this.search = search; 2643 this.size = size; 2644 searchStuff = getSearchSqlPieces(from, where, tableAliases); 2645 2646 int numQueries = searchStuff.size(); 2647 stmtList = new ArrayList (numQueries); 2648 rsList = new ArrayList (numQueries); 2649 2650 long queryStartTime = System.currentTimeMillis(); 2651 searchStmt = conn.createStatement(); 2652 String searchSql = (String )searchStuff.get(0); 2653 try 2654 { 2655 searchRS = searchStmt.executeQuery(searchSql); 2656 logTime(searchSql + 2657 "\nTime to only execute the query, not return results.", 2658 System.currentTimeMillis() - queryStartTime, 2659 50L, 500L); 2660 } 2661 catch (SQLException e) 2662 { 2663 Log.get(LOGGER).warn("Search sql:\n" + searchSql + 2664 "\nresulted in an exception: " + e.getMessage()); 2665 throw e; 2667 } 2668 } 2669 2670 public int size() 2671 { 2672 return size; 2673 } 2674 2675 2678 private QueryResult nextQueryResult; 2679 public Object next() 2680 { 2681 if (hasNext()) 2682 { 2683 hasNext = null; 2684 return nextQueryResult; 2685 } 2686 else 2687 { 2688 throw new NoSuchElementException ("Iterator is exhausted"); } 2690 } 2691 2692 2693 private Boolean hasNext; 2694 public boolean hasNext() 2695 { 2696 if (hasNext == null) 2697 { 2698 hasNext = (prepareNextQueryResult()) 2699 ? Boolean.TRUE : Boolean.FALSE; 2700 } 2701 return hasNext.booleanValue(); 2702 } 2703 2704 2705 public void remove() 2706 { 2707 throw new UnsupportedOperationException ( 2708 "'remove' is not implemented"); } 2710 2711 2712 int index = -1; 2713 2719 private boolean prepareNextQueryResult() 2720 { 2721 boolean anyMoreResults; 2722 try 2723 { 2724 anyMoreResults = doPrepareNextQueryResult(); 2725 } 2726 catch (Exception e) 2727 { 2728 anyMoreResults = false; 2729 Log.get(LOGGER).warn( 2730 "An exception prevented getting the next result.", e); 2731 } 2732 return anyMoreResults; 2733 } 2734 2735 private boolean doPrepareNextQueryResult() 2736 throws SQLException 2737 { 2738 boolean anyMoreResults = true; 2739 2740 if (index < 0 || index >= 1000) 2741 { 2742 if (cachedQRs == null) 2743 { 2744 cachedQRs = new QueryResult[1000]; 2745 } 2746 else 2747 { 2748 for (int i = cachedQRs.length - 1; i >= 0; i--) 2750 { 2751 cachedQRs[i] = null; 2752 } 2753 closeStatementsAndResultSets(); 2754 } 2755 2756 int count = 0; 2757 QueryResult qr; 2758 String previousPK = null; 2759 StringBuffer pks = new StringBuffer (512); 2760 while (count < 1000 && searchRS.next()) 2761 { 2762 String pk = searchRS.getString(1); 2763 if (!pk.equals(previousPK)) 2764 { 2765 previousPK = pk; 2766 pks.append(pk).append(','); 2767 qr = new QueryResult(search); 2768 qr.setIssueId(pk); 2769 qr.setIdPrefix(searchRS.getString(2)); 2770 qr.setIdCount(searchRS.getString(3)); 2771 qr.setModuleId(new Integer (searchRS.getInt(4))); 2772 qr.setIssueTypeId(new Integer (searchRS.getInt(5))); 2773 cachedQRs[count++] = qr; 2774 } 2775 } 2776 2777 anyMoreResults = count > 0; 2778 if (anyMoreResults) 2779 { 2780 index = 0; 2781 pks.setLength(pks.length() - 1); 2782 2783 if (searchStuff.size() > 1) 2785 { 2786 Iterator i = searchStuff.iterator(); 2787 i.next(); 2788 while (i.hasNext()) 2789 { 2790 ColumnBundle cb = (ColumnBundle)i.next(); 2791 StringBuffer sql = new StringBuffer (512); 2792 sql.append(cb.select); 2793 2794 sql.append(FROM).append(IssuePeer.TABLE_NAME); 2795 if (cb.outerJoins != null) 2796 { 2797 sql.append(cb.outerJoins); 2798 } 2799 sql.append(WHERE).append(IssuePeer.ISSUE_ID) 2800 .append(IN).append(pks).append(')'); 2801 addOrderByClause(sql, cb.sortColumn); 2802 Statement stmt = conn.createStatement(); 2803 ResultSet rs = stmt.executeQuery(sql.toString()); 2804 rs.next(); 2805 stmtList.add(stmt); 2806 rsList.add(new ResultSetAndSize(rs, cb.size)); 2807 } 2808 } 2809 } 2810 } 2811 else if (cachedQRs[index] == null) 2812 { 2813 anyMoreResults = false; 2814 } 2815 2816 if (anyMoreResults) 2817 { 2818 if (index > 0) 2820 { 2821 cachedQRs[index-1] = null; 2822 } 2823 nextQueryResult = cachedQRs[index++]; 2824 buildQueryResult(nextQueryResult); 2825 } 2826 2827 return anyMoreResults; 2828 } 2829 2830 2831 2839 private void buildQueryResult(QueryResult qr) 2840 throws SQLException 2841 { 2842 String queryResultPK = qr.getIssueId(); 2843 Logger scarabLog = Log.get("org.tigris.scarab"); 2844 2845 int index = 0; 2847 for (Iterator iter = rsList.iterator(); iter.hasNext();) 2848 { 2849 ResultSetAndSize rss = (ResultSetAndSize)iter.next(); 2850 ResultSet resultSet = rss.resultSet; 2851 int size = rss.size; 2852 String pk = resultSet.getString(1); 2853 while (queryResultPK.equals(pk)) 2854 { 2855 List values = qr.getAttributeValues(); 2856 if (values == null || index >= values.size()) 2862 { 2863 queryResultStarted(resultSet, qr, size); 2864 if (scarabLog.isDebugEnabled()) 2865 { 2866 scarabLog.debug("Fetching query result at index " 2867 + index + " with ID of " 2868 + queryResultPK); 2869 } 2870 } 2871 else 2872 { 2873 queryResultContinued(resultSet, qr, index, size); 2874 } 2875 2876 pk = (resultSet.next()) ? resultSet.getString(1): null; 2877 } 2878 index += size; 2879 } 2880 } 2881 2882 private void queryResultStarted(ResultSet rs, QueryResult qr, 2883 int valueListSize) 2884 throws SQLException 2885 { 2886 if (valueListSize > 0) 2887 { 2888 List values = new ArrayList (valueListSize); 2890 for (int j = 0; j < valueListSize; j++) 2891 { 2892 ArrayList multiVal = new ArrayList (2); 2893 multiVal.add(rs.getString(j + 4)); 2894 values.add(multiVal); 2895 } 2896 List lastValues = qr.getAttributeValues(); 2897 if (lastValues == null) 2898 { 2899 qr.setAttributeValues(values); 2900 } 2901 else 2902 { 2903 lastValues.addAll(values); 2904 } 2905 } 2906 } 2907 2908 private void queryResultContinued(ResultSet rs, QueryResult qr, 2909 int base, int valueListSize) 2910 throws SQLException 2911 { 2912 if (valueListSize > 0) 2913 { 2914 List values = qr.getAttributeValues(); 2915 for (int j = 0; j < valueListSize; j++) 2916 { 2917 String s = rs.getString(j + 4); 2918 2919 List prevValues = (List ) values.get(j + base); 2928 boolean newValue = true; 2929 for (int k = 0; k < prevValues.size(); k++) 2930 { 2931 if (ObjectUtils.equals(prevValues.get(k), s)) 2932 { 2933 newValue = false; 2934 break; 2935 } 2936 } 2937 if (newValue) 2938 { 2939 prevValues.add(s); 2940 } 2941 } 2942 } 2943 } 2944 } 2945 2946 private static class ColumnBundle 2947 { 2948 int size; 2949 StringBuffer select; 2950 StringBuffer outerJoins; 2951 String sortColumn; 2952 } 2953 2954 private static class ResultSetAndSize 2955 { 2956 private ResultSet resultSet; 2957 private int size; 2958 ResultSetAndSize(ResultSet rs, int s) 2959 { 2960 resultSet = rs; 2961 size = s; 2962 } 2963 } 2964} 2965 2966 | Popular Tags |