1 package org.apache.torque.util; 2 3 21 22 import java.io.IOException ; 23 import java.io.ObjectInputStream ; 24 import java.io.Serializable ; 25 import java.lang.reflect.Method ; 26 import java.sql.Connection ; 27 import java.sql.SQLException ; 28 import java.util.ArrayList ; 29 import java.util.Hashtable ; 30 import java.util.Iterator ; 31 import java.util.List ; 32 import java.util.Set ; 33 34 import org.apache.commons.logging.Log; 35 import org.apache.commons.logging.LogFactory; 36 import org.apache.torque.Torque; 37 import org.apache.torque.TorqueException; 38 39 import com.workingdogs.village.DataSetException; 40 import com.workingdogs.village.QueryDataSet; 41 42 138 public class LargeSelect implements Runnable , Serializable 139 { 140 141 private static final long serialVersionUID = -1166842932571491942L; 142 143 144 private int pageSize; 145 146 private int memoryLimit; 147 148 149 private transient int blockBegin = 0; 150 151 private transient int blockEnd; 152 153 private volatile int currentlyFilledTo = -1; 154 155 156 private String query; 157 158 private String dbName; 159 160 161 private transient List results = null; 162 163 164 private transient Thread thread = null; 165 169 private transient volatile boolean killThread = false; 170 171 private transient volatile boolean threadRunning = false; 172 176 private transient volatile boolean queryCompleted = false; 177 181 private transient boolean totalsFinalized = false; 182 183 184 private int position; 185 186 private int totalPages = -1; 187 188 private int totalRecords = 0; 189 190 191 private Criteria criteria = null; 192 193 private transient List lastResults; 194 195 199 private Class returnBuilderClass = null; 200 204 private transient Method populateObjectsMethod = null; 205 206 210 public static final String DEFAULT_MORE_INDICATOR = ">"; 211 212 217 private static String moreIndicator = DEFAULT_MORE_INDICATOR; 218 219 223 public static final int DEFAULT_MEMORY_LIMIT_PAGES = 5; 224 225 229 private static int memoryPageLimit = DEFAULT_MEMORY_LIMIT_PAGES; 230 231 235 private static final int QUERY_NOT_COMPLETED_SLEEP_TIME = 500; 236 237 240 private static final int QUERY_STOP_SLEEP_TIME = 100; 241 242 243 private Hashtable params = null; 244 245 246 private static Log log = LogFactory.getLog(LargeSelect.class); 247 248 262 public LargeSelect(Criteria criteria, int pageSize) 263 { 264 this(criteria, pageSize, LargeSelect.memoryPageLimit); 265 } 266 267 284 public LargeSelect(Criteria criteria, int pageSize, int memoryPageLimit) 285 { 286 init(criteria, pageSize, memoryPageLimit); 287 } 288 289 313 public LargeSelect( 314 Criteria criteria, 315 int pageSize, 316 String returnBuilderClassName) 317 { 318 this( 319 criteria, 320 pageSize, 321 LargeSelect.memoryPageLimit, 322 returnBuilderClassName); 323 } 324 325 352 public LargeSelect( 353 Criteria criteria, 354 int pageSize, 355 int memoryPageLimit, 356 String returnBuilderClassName) 357 { 358 try 359 { 360 this.returnBuilderClass = Class.forName(returnBuilderClassName); 361 362 if (criteria.getSelectColumns().size() == 0) 364 { 365 Class [] argTypes = { Criteria.class }; 366 Method selectColumnAdder = 367 returnBuilderClass.getMethod("addSelectColumns", argTypes); 368 Object [] theArgs = { criteria }; 369 selectColumnAdder.invoke(returnBuilderClass.newInstance(), 370 theArgs); 371 } 372 } 373 catch (Exception e) 374 { 375 throw new IllegalArgumentException ( 376 "The class named as returnBuilderClassName does not " 377 + "provide the necessary facilities - see javadoc."); 378 } 379 380 init(criteria, pageSize, memoryPageLimit); 381 } 382 383 391 private Method getPopulateObjectsMethod() 392 throws NoSuchMethodException 393 { 394 if (null == populateObjectsMethod) 395 { 396 Class [] argTypes = { List .class }; 397 populateObjectsMethod 398 = returnBuilderClass.getMethod("populateObjects", argTypes); 399 } 400 return populateObjectsMethod; 401 } 402 403 417 private void init(Criteria criteria, int pageSize, int memoryLimitPages) 418 { 419 if (criteria.getOffset() != 0 || criteria.getLimit() != -1) 420 { 421 throw new IllegalArgumentException ( 422 "criteria must not use Offset and/or Limit."); 423 } 424 425 if (pageSize < 1) 426 { 427 throw new IllegalArgumentException ( 428 "pageSize must be greater than zero."); 429 } 430 431 if (memoryLimitPages < 1) 432 { 433 throw new IllegalArgumentException ( 434 "memoryPageLimit must be greater than zero."); 435 } 436 437 this.pageSize = pageSize; 438 this.memoryLimit = pageSize * memoryLimitPages; 439 this.criteria = criteria; 440 dbName = criteria.getDbName(); 441 blockEnd = blockBegin + memoryLimit - 1; 442 startQuery(pageSize); 443 } 444 445 458 public List getPage(int pageNumber) throws TorqueException 459 { 460 if (pageNumber < 1) 461 { 462 throw new IllegalArgumentException ( 463 "pageNumber must be greater than zero."); 464 } 465 return getResults((pageNumber - 1) * pageSize); 466 } 467 468 476 public List getNextResults() throws TorqueException 477 { 478 if (!getNextResultsAvailable()) 479 { 480 return getCurrentPageResults(); 481 } 482 return getResults(position); 483 } 484 485 493 public List getCurrentPageResults() throws TorqueException 494 { 495 return null == lastResults && position > 0 496 ? getResults(position) : lastResults; 497 } 498 499 507 public List getPreviousResults() throws TorqueException 508 { 509 if (!getPreviousResultsAvailable()) 510 { 511 return getCurrentPageResults(); 512 } 513 514 int start; 515 if (position - 2 * pageSize < 0) 516 { 517 start = 0; 518 } 519 else 520 { 521 start = position - 2 * pageSize; 522 } 523 return getResults(start); 524 } 525 526 535 private List getResults(int start) throws TorqueException 536 { 537 return getResults(start, pageSize); 538 } 539 540 554 private synchronized List getResults(int start, int size) 555 throws TorqueException 556 { 557 if (log.isDebugEnabled()) 558 { 559 log.debug("getResults(start: " + start 560 + ", size: " + size + ") invoked."); 561 } 562 563 if (size > memoryLimit) 564 { 565 throw new IllegalArgumentException ("size (" + size 566 + ") exceeds memory limit (" + memoryLimit + ")."); 567 } 568 569 if (start >= blockBegin && (start + size - 1) <= blockEnd) 573 { 574 if (log.isDebugEnabled()) 575 { 576 log.debug("getResults(): Sleeping until " 577 + "start+size-1 (" + (start + size - 1) 578 + ") > currentlyFilledTo (" + currentlyFilledTo 579 + ") && !queryCompleted (!" + queryCompleted + ")"); 580 } 581 while (((start + size - 1) > currentlyFilledTo) && !queryCompleted) 582 { 583 try 584 { 585 Thread.sleep(QUERY_NOT_COMPLETED_SLEEP_TIME); 586 } 587 catch (InterruptedException e) 588 { 589 throw new TorqueException("Unexpected interruption", e); 590 } 591 } 592 } 593 594 else if (start < blockBegin && start >= 0) 597 { 598 if (log.isDebugEnabled()) 599 { 600 log.debug("getResults(): Paging backwards as start (" + start 601 + ") < blockBegin (" + blockBegin + ") && start >= 0"); 602 } 603 stopQuery(); 604 if (memoryLimit >= 2 * size) 605 { 606 blockBegin = start - size; 607 if (blockBegin < 0) 608 { 609 blockBegin = 0; 610 } 611 } 612 else 613 { 614 blockBegin = start; 615 } 616 blockEnd = blockBegin + memoryLimit - 1; 617 startQuery(size); 618 return getResults(start, size); 620 } 621 622 else if ((start + size - 1) > blockEnd) 624 { 625 if (log.isDebugEnabled()) 626 { 627 log.debug("getResults(): Paging past end of loaded data as " 628 + "start+size-1 (" + (start + size - 1) 629 + ") > blockEnd (" + blockEnd + ")"); 630 } 631 stopQuery(); 632 blockBegin = start; 633 blockEnd = blockBegin + memoryLimit - 1; 634 startQuery(size); 635 return getResults(start, size); 637 } 638 639 else 640 { 641 throw new IllegalArgumentException ("Parameter configuration not " 642 + "accounted for."); 643 } 644 645 int fromIndex = start - blockBegin; 646 int toIndex = fromIndex + Math.min(size, results.size() - fromIndex); 647 648 if (log.isDebugEnabled()) 649 { 650 log.debug("getResults(): Retrieving records from results elements " 651 + "start-blockBegin (" + fromIndex + ") through " 652 + "fromIndex + Math.min(size, results.size() - fromIndex) (" 653 + toIndex + ")"); 654 } 655 656 List returnResults; 657 658 synchronized (results) 659 { 660 returnResults = new ArrayList (results.subList(fromIndex, toIndex)); 661 } 662 663 if (null != returnBuilderClass) 664 { 665 Object [] theArgs = { returnResults }; 667 try 668 { 669 returnResults = (List ) getPopulateObjectsMethod().invoke( 670 returnBuilderClass.newInstance(), theArgs); 671 } 672 catch (Exception e) 673 { 674 throw new TorqueException("Unable to populate results", e); 675 } 676 } 677 position = start + size; 678 lastResults = returnResults; 679 return returnResults; 680 } 681 682 685 public void run() 686 { 687 boolean dbSupportsNativeLimit; 688 boolean dbSupportsNativeOffset; 689 try 690 { 691 dbSupportsNativeLimit 692 = (Torque.getDB(dbName).supportsNativeLimit()); 693 dbSupportsNativeOffset 694 = (Torque.getDB(dbName).supportsNativeOffset()); 695 } 696 catch (TorqueException e) 697 { 698 log.error("run() : Exiting :", e); 699 return; 702 } 703 704 int size; 705 if (dbSupportsNativeLimit && dbSupportsNativeOffset) 706 { 707 size = pageSize; 709 } 710 else 711 { 712 size = blockBegin + memoryLimit + 1; 716 } 717 718 Connection conn = null; 719 720 QueryDataSet qds = null; 721 722 try 723 { 724 results = new ArrayList (memoryLimit + 1); 726 727 if (dbSupportsNativeLimit) 730 { 731 if (dbSupportsNativeOffset) 732 { 733 criteria.setOffset(blockBegin); 734 criteria.setLimit(memoryLimit + 1); 737 } 738 else 739 { 740 criteria.setLimit(blockBegin + memoryLimit + 1); 741 } 742 } 743 query = BasePeer.createQueryString(criteria); 744 745 conn = Torque.getConnection(dbName); 747 748 if (log.isDebugEnabled()) 750 { 751 log.debug("run(): query = " + query); 752 log.debug("run(): memoryLimit = " + memoryLimit); 753 log.debug("run(): blockBegin = " + blockBegin); 754 log.debug("run(): blockEnd = " + blockEnd); 755 } 756 qds = new QueryDataSet(conn, query); 757 758 while (!killThread 762 && !qds.allRecordsRetrieved() 763 && currentlyFilledTo + pageSize <= blockEnd) 764 { 765 if ((currentlyFilledTo + pageSize) >= blockEnd 770 && dbSupportsNativeLimit) 771 { 772 size = blockEnd - currentlyFilledTo + 1; 774 } 775 776 if (log.isDebugEnabled()) 777 { 778 log.debug("run(): Invoking BasePeer.getSelectResults(qds, " 779 + size + ", false)"); 780 } 781 782 List tempResults 783 = BasePeer.getSelectResults(qds, size, false); 784 785 int startIndex = dbSupportsNativeOffset ? 0 : blockBegin; 786 787 synchronized (results) 788 { 789 for (int i = startIndex, n = tempResults.size(); i < n; i++) 790 { 791 results.add(tempResults.get(i)); 792 } 793 } 794 795 if (dbSupportsNativeLimit && dbSupportsNativeOffset) 796 { 797 currentlyFilledTo += tempResults.size(); 798 } 799 else 800 { 801 currentlyFilledTo = tempResults.size() - 1 - blockBegin; 802 } 803 804 boolean perhapsLastPage = true; 805 806 if ((dbSupportsNativeLimit 809 && (results.size() == memoryLimit + 1)) 810 || (!dbSupportsNativeLimit 811 && currentlyFilledTo >= memoryLimit)) 812 { 813 synchronized (results) 814 { 815 results.remove(currentlyFilledTo--); 816 } 817 perhapsLastPage = false; 818 } 819 820 if (results.size() > 0 821 && blockBegin + currentlyFilledTo >= totalRecords) 822 { 823 totalRecords = blockBegin + currentlyFilledTo + 1; 825 } 826 827 if (qds.allRecordsRetrieved() 831 || !dbSupportsNativeLimit) 832 { 833 queryCompleted = true; 834 if (perhapsLastPage 838 && getCurrentPageNumber() <= getTotalPages()) 839 { 840 totalsFinalized = true; 841 } 842 } 843 qds.clearRecords(); 844 } 845 846 if (log.isDebugEnabled()) 847 { 848 log.debug("run(): While loop terminated because either:"); 849 log.debug("run(): 1. qds.allRecordsRetrieved(): " 850 + qds.allRecordsRetrieved()); 851 log.debug("run(): 2. killThread: " + killThread); 852 log.debug("run(): 3. !(currentlyFilledTo + size <= blockEnd): !" 853 + (currentlyFilledTo + pageSize <= blockEnd)); 854 log.debug("run(): - currentlyFilledTo: " + currentlyFilledTo); 855 log.debug("run(): - size: " + pageSize); 856 log.debug("run(): - blockEnd: " + blockEnd); 857 log.debug("run(): - results.size(): " + results.size()); 858 } 859 } 860 catch (TorqueException e) 861 { 862 log.error(e); 863 } 864 catch (SQLException e) 865 { 866 log.error(e); 867 } 868 catch (DataSetException e) 869 { 870 log.error(e); 871 } 872 finally 873 { 874 try 875 { 876 if (qds != null) 877 { 878 qds.close(); 879 } 880 Torque.closeConnection(conn); 881 } 882 catch (SQLException e) 883 { 884 log.error(e); 885 } 886 catch (DataSetException e) 887 { 888 log.error(e); 889 } 890 threadRunning = false; 891 } 892 } 893 894 899 private synchronized void startQuery(int initialSize) 900 { 901 if (!threadRunning) 902 { 903 pageSize = initialSize; 904 currentlyFilledTo = -1; 905 queryCompleted = false; 906 thread = new Thread (this); 907 thread.start(); 908 threadRunning = true; 909 } 910 } 911 912 918 private synchronized void stopQuery() throws TorqueException 919 { 920 if (threadRunning) 921 { 922 killThread = true; 923 while (thread.isAlive()) 924 { 925 try 926 { 927 Thread.sleep(QUERY_STOP_SLEEP_TIME); 928 } 929 catch (InterruptedException e) 930 { 931 throw new TorqueException("Unexpected interruption", e); 932 } 933 } 934 killThread = false; 935 } 936 } 937 938 943 public int getCurrentPageNumber() 944 { 945 return position / pageSize; 946 } 947 948 958 public int getTotalRecords() 959 { 960 return totalRecords; 961 } 962 963 969 public boolean getPaginated() 970 { 971 if (!getTotalsFinalized()) 973 { 974 return true; 975 } 976 return blockBegin + currentlyFilledTo + 1 > pageSize; 977 } 978 979 989 public int getTotalPages() 990 { 991 if (totalPages > -1) 992 { 993 return totalPages; 994 } 995 996 int tempPageCount = getTotalRecords() / pageSize 997 + (getTotalRecords() % pageSize > 0 ? 1 : 0); 998 999 if (getTotalsFinalized()) 1000 { 1001 totalPages = tempPageCount; 1002 } 1003 1004 return tempPageCount; 1005 } 1006 1007 1013 public int getPageSize() 1014 { 1015 return pageSize; 1016 } 1017 1018 1025 public boolean getTotalsFinalized() 1026 { 1027 return totalsFinalized; 1028 } 1029 1030 1036 public static void setMoreIndicator(String moreIndicator) 1037 { 1038 LargeSelect.moreIndicator = moreIndicator; 1039 } 1040 1041 1044 public static String getMoreIndicator() 1045 { 1046 return LargeSelect.moreIndicator; 1047 } 1048 1049 1057 public static void setMemoryPageLimit(int memoryPageLimit) 1058 { 1059 LargeSelect.memoryPageLimit = memoryPageLimit; 1060 } 1061 1062 1067 public static int getMemoryPageLimit() 1068 { 1069 return LargeSelect.memoryPageLimit; 1070 } 1071 1072 1079 public String getPageProgressText() 1080 { 1081 StringBuffer result = new StringBuffer (); 1082 result.append(getCurrentPageNumber()); 1083 result.append(" of "); 1084 if (!totalsFinalized) 1085 { 1086 result.append(moreIndicator); 1087 result.append(" "); 1088 } 1089 result.append(getTotalPages()); 1090 return result.toString(); 1091 } 1092 1093 1102 public int getCurrentPageSize() throws TorqueException 1103 { 1104 if (null == getCurrentPageResults()) 1105 { 1106 return 0; 1107 } 1108 return getCurrentPageResults().size(); 1109 } 1110 1111 1116 public int getFirstRecordNoForPage() 1117 { 1118 if (getCurrentPageNumber() < 1) 1119 { 1120 return 0; 1121 } 1122 return (getCurrentPageNumber() - 1) * getPageSize() + 1; 1123 } 1124 1125 1132 public int getLastRecordNoForPage() throws TorqueException 1133 { 1134 if (0 == getCurrentPageNumber()) 1135 { 1136 return 0; 1137 } 1138 return (getCurrentPageNumber() - 1) * getPageSize() 1139 + getCurrentPageSize(); 1140 } 1141 1142 1151 public String getRecordProgressText() throws TorqueException 1152 { 1153 StringBuffer result = new StringBuffer (); 1154 result.append(getFirstRecordNoForPage()); 1155 result.append(" - "); 1156 result.append(getLastRecordNoForPage()); 1157 result.append(" of "); 1158 if (!totalsFinalized) 1159 { 1160 result.append(moreIndicator); 1161 result.append(" "); 1162 } 1163 result.append(getTotalRecords()); 1164 return result.toString(); 1165 } 1166 1167 1172 public boolean getNextResultsAvailable() 1173 { 1174 if (!totalsFinalized || getCurrentPageNumber() < getTotalPages()) 1175 { 1176 return true; 1177 } 1178 return false; 1179 } 1180 1181 1186 public boolean getPreviousResultsAvailable() 1187 { 1188 if (getCurrentPageNumber() <= 1) 1189 { 1190 return false; 1191 } 1192 return true; 1193 } 1194 1195 1200 public boolean hasResultsAvailable() 1201 { 1202 return getTotalRecords() > 0; 1203 } 1204 1205 1212 public synchronized void invalidateResult() throws TorqueException 1213 { 1214 stopQuery(); 1215 blockBegin = 0; 1216 blockEnd = 0; 1217 currentlyFilledTo = -1; 1218 results = null; 1219 position = 0; 1223 totalPages = -1; 1224 totalRecords = 0; 1225 queryCompleted = false; 1226 totalsFinalized = false; 1227 lastResults = null; 1228 } 1229 1230 1240 public String getSearchParam(String name) 1241 { 1242 return getSearchParam(name, null); 1243 } 1244 1245 1256 public String getSearchParam(String name, String defaultValue) 1257 { 1258 if (null == params) 1259 { 1260 return defaultValue; 1261 } 1262 String value = (String ) params.get(name); 1263 return null == value ? defaultValue : value; 1264 } 1265 1266 1273 public void setSearchParam(String name, String value) 1274 { 1275 if (null == value) 1276 { 1277 removeSearchParam(name); 1278 } 1279 else 1280 { 1281 if (null != name) 1282 { 1283 if (null == params) 1284 { 1285 params = new Hashtable (); 1286 } 1287 params.put(name, value); 1288 } 1289 } 1290 } 1291 1292 1297 public void removeSearchParam(String name) 1298 { 1299 if (null != params) 1300 { 1301 params.remove(name); 1302 } 1303 } 1304 1305 1312 private void readObject(ObjectInputStream inputStream) 1313 throws IOException , ClassNotFoundException 1314 { 1315 inputStream.defaultReadObject(); 1316 1317 if (Torque.isInit()) 1319 { 1320 startQuery(pageSize); 1321 } 1322 } 1323 1324 1329 public String toString() 1330 { 1331 StringBuffer result = new StringBuffer (); 1332 result.append("LargeSelect - TotalRecords: "); 1333 result.append(getTotalRecords()); 1334 result.append(" TotalsFinalised: "); 1335 result.append(getTotalsFinalized()); 1336 result.append("\nParameters:"); 1337 if (null == params || params.size() == 0) 1338 { 1339 result.append(" No parameters have been set."); 1340 } 1341 else 1342 { 1343 Set keys = params.keySet(); 1344 for (Iterator iter = keys.iterator(); iter.hasNext();) 1345 { 1346 String key = (String ) iter.next(); 1347 String val = (String ) params.get(key); 1348 result.append("\n ").append(key).append(": ").append(val); 1349 } 1350 } 1351 return result.toString(); 1352 } 1353 1354} 1355 | Popular Tags |