Java tutorial
package org.apache.torque.util; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.sql.Connection; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.torque.Torque; import org.apache.torque.TorqueException; import org.apache.torque.criteria.CriteriaInterface; import org.apache.torque.sql.SqlBuilder; /** * This class can be used to retrieve a large result set from a database query. * The query is started and then rows are returned a page at a time. The <code> * LargeSelect</code> is meant to be placed into the Session or User.Temp, so * that it can be used in response to several related requests. Note that in * order to use <code>LargeSelect</code> you need to be willing to accept the * fact that the result set may become inconsistent with the database if updates * are processed subsequent to the queries being executed. Specifying a memory * page limit of 1 will give you a consistent view of the records but the totals * may not be accurate and the performance will be terrible. In most cases * the potential for inconsistencies data should not cause any serious problems * and performance should be pretty good (but read on for further warnings). * * <p>The idea here is that the full query result would consume too much memory * and if displayed to a user the page would be too long to be useful. Rather * than loading the full result set into memory, a window of data (the memory * limit) is loaded and retrieved a page at a time. If a request occurs for * data that falls outside the currently loaded window of data then a new query * is executed to fetch the required data. Performance is optimized by * starting a thread to execute the database query and fetch the results. This * will perform best when paging forwards through the data, but a minor * optimization where the window is moved backwards by two rather than one page * is included for when a user pages past the beginning of the window. * * <p>As the query is performed in in steps, it is often the case that the total * number of records and pages of data is unknown. <code>LargeSelect</code> * provides various methods for indicating how many records and pages it is * currently aware of and for presenting this information to users. * * <p><code>LargeSelect</code> utilizes the <code>Criteria</code> methods * <code>setOffset()</code> and <code>setLimit()</code> to limit the amount of * data retrieved from the database - these values are either passed through to * the DBMS when supported (efficient with the caveat below) or handled by * the Torque API when it is not (not so efficient). * * <p>As <code>LargeSelect</code> must re-execute the query each time the user * pages out of the window of loaded data, you should consider the impact of * non-index sort orderings and other criteria that will require the DBMS to * execute the entire query before filtering down to the offset and limit either * internally or via Torque. * * <p>The memory limit defaults to 5 times the page size you specify, but * alternative constructors and the class method <code>setMemoryPageLimit() * </code> allow you to override this for a specific instance of * <code>LargeSelect</code> or future instances respectively. * * <p>Typically you will create a <code>LargeSelect</code> using your <code> * Criteria</code> (perhaps created from the results of a search parameter * page), page size, memory page limit, return class name (for which you may * have defined a business object class before hand) and peer class * and place this in user.Temp thus: * * <pre> * data.getUser().setTemp("someName", largeSelect); * </pre> * * <p>In your template you will then use something along the lines of: * * <pre> * #set($largeSelect = $data.User.getTemp("someName")) * #set($searchop = $data.Parameters.getString("searchop")) * #if($searchop.equals("prev")) * #set($recs = $largeSelect.PreviousResults) * #else * #if($searchop.equals("goto")) * #set($recs = $largeSelect.getPage($data.Parameters.getInt("page", 1))) * #else * #set($recs = $largeSelect.NextResults) * #end * #end * </pre> * * <p>...to move through the records. <code>LargeSelect</code> implements a * number of convenience methods that make it easy to add all of the necessary * bells and whistles to your template. * * @param <T> the type of the objects which are returned on a query. * * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a> * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a> * @version $Id: LargeSelect.java 1377560 2012-08-27 03:25:41Z tfischer $ */ public class LargeSelect<T> implements Runnable, Serializable { /** Serial version */ private static final long serialVersionUID = -1166842932571491942L; /** The number of records that a page consists of. */ private int pageSize; /** The maximum number of records to maintain in memory. */ private int memoryLimit; /** The record number of the first record in memory. */ private transient volatile int blockBegin = 0; /** The record number of the last record in memory. */ private transient volatile int blockEnd; /** How much of the memory block is currently occupied with result data. */ private volatile int currentlyFilledTo = -1; /** The memory store of results. */ private transient List<T> results = null; /** The thread that executes the query. */ private transient Thread thread = null; /** * A flag used to kill the thread when the currently executing query is no * longer required. */ private transient volatile boolean killThread = false; /** A flag that indicates whether or not the query thread is running. */ private transient volatile boolean threadRunning = false; /** * An indication of whether or not the current query has completed * processing. */ private transient volatile boolean queryCompleted = false; /** * An indication of whether or not the totals (records and pages) are at * their final values. */ private transient boolean totalsFinalized = false; /** The cursor position in the result set. */ private int position; /** The total number of pages known to exist. */ private int totalPages = -1; /** The total number of records known to exist. */ private int totalRecords = 0; /** The criteria used for the query. */ private CriteriaInterface<?> criteria = null; /** The last page of results that were returned. */ private transient List<T> lastResults; /** * The BasePeerImpl object that handles database selects. */ private BasePeerImpl<T> peer = null; /** * The default value (">") used to indicate that the total number of * records or pages is unknown. */ public static final String DEFAULT_MORE_INDICATOR = ">"; /** * The value used to indicate that the total number of records or pages is * unknown (default: ">"). You can use <code>setMoreIndicator()</code> * to change this to whatever value you like (e.g. "more than"). */ private String moreIndicator = DEFAULT_MORE_INDICATOR; /** * The default value for the maximum number of pages of data to be retained * in memory. */ public static final int DEFAULT_MEMORY_LIMIT_PAGES = 5; /** * The maximum number of pages of data to be retained in memory. Use * <code>setMemoryPageLimit()</code> to provide your own value. */ private int memoryPageLimit = DEFAULT_MEMORY_LIMIT_PAGES; /** * The number of milliseconds to sleep when the result of a query * is not yet available. */ private static final int QUERY_NOT_COMPLETED_SLEEP_TIME = 500; /** * The number of milliseconds to sleep before retrying to stop a query. */ private static final int QUERY_STOP_SLEEP_TIME = 100; /** A place to store search parameters that relate to this query. */ private Map<String, String> params = null; /** Logging */ private static Log log = LogFactory.getLog(LargeSelect.class); /** * Creates a LargeSelect whose results are returned as a <code>List</code> * containing a maximum of <code>pageSize</code> objects of the type * defined within the class named <code>returnBuilderClassName</code> at a * time, maintaining a maximum of <code>LargeSelect.memoryPageLimit</code> * pages of results in memory. * * @param criteria object used by BasePeer to build the query. In order to * allow this class to utilize database server implemented offsets and * limits (when available), the provided criteria must not have any limit or * offset defined. * * @param pageSize number of rows to return in one block. * @param peerImpl the peer that will be used to do the select operations * * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit, if <code>pageSize</code> is less than 1 or * the Criteria object does not contain SELECT columns. */ public LargeSelect(CriteriaInterface<?> criteria, int pageSize, BasePeerImpl<T> peerImpl) { this(criteria, pageSize, LargeSelect.DEFAULT_MEMORY_LIMIT_PAGES, peerImpl); } /** * Creates a LargeSelect whose results are returned as a <code>List</code> * containing a maximum of <code>pageSize</code> objects of the type T at a * time, maintaining a maximum of <code>memoryPageLimit</code> pages of * results in memory. * * @param criteria object used by BasePeerImpl to build the query. In order to * allow this class to utilize database server implemented offsets and * limits (when available), the provided criteria must not have any limit or * offset defined. * * @param pageSize number of rows to return in one block. * @param memoryPageLimit maximum number of pages worth of rows to be held * in memory at one time. * @param peerImpl the peer that will be used to do the select operations * * @throws IllegalArgumentException if <code>criteria</code> uses one or * both of offset and limit, if <code>pageSize</code> or <code> * memoryLimitPages</code> are less than 1 or the Criteria object does not * contain SELECT columns.. */ public LargeSelect(CriteriaInterface<?> criteria, int pageSize, int memoryPageLimit, BasePeerImpl<T> peerImpl) { this.peer = peerImpl; if (criteria.getOffset() != 0 || criteria.getLimit() != -1) { throw new IllegalArgumentException("criteria must not use Offset and/or Limit."); } if (pageSize < 1) { throw new IllegalArgumentException("pageSize must be greater than zero."); } if (memoryPageLimit < 1) { throw new IllegalArgumentException("memoryPageLimit must be greater than zero."); } this.pageSize = pageSize; this.memoryLimit = pageSize * memoryPageLimit; this.criteria = criteria; blockEnd = blockBegin + memoryLimit - 1; startQuery(pageSize); } /** * Retrieve a specific page, if it exists. * * @param pageNumber the number of the page to be retrieved - must be * greater than zero. An empty <code>List</code> will be returned if * <code>pageNumber</code> exceeds the total number of pages that exist. * @return a <code>List</code> of query results containing a maximum of * <code>pageSize</code> results. * @throws IllegalArgumentException when <code>pageNo</code> is not * greater than zero. * @throws TorqueException if a sleep is unexpectedly interrupted. */ public List<T> getPage(int pageNumber) throws TorqueException { if (pageNumber < 1) { throw new IllegalArgumentException("pageNumber must be greater than zero."); } return getResults((pageNumber - 1) * pageSize); } /** * Gets the next page of rows. * * @return a <code>List</code> of query results containing a maximum of * <code>pageSize</code> results. * @throws TorqueException if a sleep is unexpectedly interrupted. */ public List<T> getNextResults() throws TorqueException { if (!getNextResultsAvailable()) { return getCurrentPageResults(); } return getResults(position); } /** * Provide access to the results from the current page. * * @return a <code>List</code> of query results containing a maximum of * <code>pageSize</code> results. * @throws TorqueException if a sleep is unexpectedly interrupted. */ public List<T> getCurrentPageResults() throws TorqueException { return null == lastResults && position > 0 ? getResults(position) : lastResults; } /** * Gets the previous page of rows. * * @return a <code>List</code> of query results containing a maximum of * <code>pageSize</code> results. * @throws TorqueException if a sleep is unexpectedly interrupted. */ public List<T> getPreviousResults() throws TorqueException { if (!getPreviousResultsAvailable()) { return getCurrentPageResults(); } int start; if (position - 2 * pageSize < 0) { start = 0; } else { start = position - 2 * pageSize; } return getResults(start); } /** * Gets a page of rows starting at a specified row. * * @param start the starting row. * @return a <code>List</code> of query results containing a maximum of * <code>pageSize</code> results. * @throws TorqueException if a sleep is unexpectedly interrupted. */ private List<T> getResults(int start) throws TorqueException { return getResults(start, pageSize); } /** * Gets a block of rows starting at a specified row and containing a * specified number of rows. * * @param start the starting row. * @param size the number of rows. * @return a <code>List</code> of query results containing a maximum of * <code>pageSize</code> results. * @throws IllegalArgumentException if <code>size > memoryLimit</code> or * <code>start</code> and <code>size</code> result in a situation that is * not catered for. * @throws TorqueException if a sleep is unexpectedly interrupted. */ private synchronized List<T> getResults(int start, int size) throws TorqueException { if (log.isDebugEnabled()) { log.debug("getResults(start: " + start + ", size: " + size + ") invoked."); } if (size > memoryLimit) { throw new IllegalArgumentException("size (" + size + ") exceeds memory limit (" + memoryLimit + ")."); } // Request was for a block of rows which should be in progress. // If the rows have not yet been returned, wait for them to be // retrieved. if (start >= blockBegin && (start + size - 1) <= blockEnd) { if (log.isDebugEnabled()) { log.debug("getResults(): Sleeping until " + "start+size-1 (" + (start + size - 1) + ") > currentlyFilledTo (" + currentlyFilledTo + ") && !queryCompleted (!" + queryCompleted + ")"); } while (((start + size - 1) > currentlyFilledTo) && !queryCompleted) { try { Thread.sleep(QUERY_NOT_COMPLETED_SLEEP_TIME); } catch (InterruptedException e) { throw new TorqueException("Unexpected interruption", e); } } } // Going in reverse direction, trying to limit db hits so assume user // might want at least 2 sets of data. else if (start < blockBegin && start >= 0) { if (log.isDebugEnabled()) { log.debug("getResults(): Paging backwards as start (" + start + ") < blockBegin (" + blockBegin + ") && start >= 0"); } stopQuery(); if (memoryLimit >= 2 * size) { blockBegin = start - size; if (blockBegin < 0) { blockBegin = 0; } } else { blockBegin = start; } blockEnd = blockBegin + memoryLimit - 1; startQuery(size); // Re-invoke getResults() to provide the wait processing. return getResults(start, size); } // Assume we are moving on, do not retrieve any records prior to start. else if ((start + size - 1) > blockEnd) { if (log.isDebugEnabled()) { log.debug("getResults(): Paging past end of loaded data as " + "start+size-1 (" + (start + size - 1) + ") > blockEnd (" + blockEnd + ")"); } stopQuery(); blockBegin = start; blockEnd = blockBegin + memoryLimit - 1; startQuery(size); // Re-invoke getResults() to provide the wait processing. return getResults(start, size); } else { throw new IllegalArgumentException("Parameter configuration not " + "accounted for."); } int fromIndex = start - blockBegin; int toIndex = fromIndex + Math.min(size, results.size() - fromIndex); if (log.isDebugEnabled()) { log.debug("getResults(): Retrieving records from results elements " + "start-blockBegin (" + fromIndex + ") through " + "fromIndex + Math.min(size, results.size() - fromIndex) (" + toIndex + ")"); } List<T> returnResults; synchronized (results) { returnResults = new ArrayList<T>(results.subList(fromIndex, toIndex)); } position = start + size; lastResults = returnResults; return returnResults; } /** * A background thread that retrieves the rows. */ public void run() { /* The connection to the database. */ Connection conn = null; try { // Add 1 to memory limit to check if the query ends on a page break. results = new ArrayList<T>(memoryLimit + 1); criteria.setOffset(blockBegin); // Add 1 to memory limit to check if the query ends on a // page break. criteria.setLimit(memoryLimit + 1); /* * Fix criterions relating to booleanint or booleanchar columns * The defaultTableMap parameter in this call is null because we have * no default peer class inside LargeSelect. This means that all * columns not fully qualified will not be modified. */ String query; if (criteria instanceof Criteria) { peer.correctBooleans((Criteria) criteria); peer.setDbName((Criteria) criteria); query = SqlBuilder.buildQuery((Criteria) criteria).toString(); } else { peer.correctBooleans((org.apache.torque.criteria.Criteria) criteria); peer.setDbName((org.apache.torque.criteria.Criteria) criteria); query = SqlBuilder.buildQuery((org.apache.torque.criteria.Criteria) criteria).toString(); } // Execute the query. if (log.isDebugEnabled()) { log.debug("run(): query = " + query); log.debug("run(): memoryLimit = " + memoryLimit); log.debug("run(): blockBegin = " + blockBegin); log.debug("run(): blockEnd = " + blockEnd); } // Get a connection to the db. conn = Transaction.begin(criteria.getDbName()); // Continue getting rows one page at a time until the memory limit // is reached, all results have been retrieved, or the rest // of the results have been determined to be irrelevant. boolean allRecordsRetrieved = false; while (!killThread && !allRecordsRetrieved && currentlyFilledTo + pageSize <= blockEnd) { if (log.isDebugEnabled()) { log.debug("run(): Invoking BasePeerImpl.doSelect()"); } List<T> tempResults; if (criteria instanceof Criteria) { tempResults = peer.doSelect((Criteria) criteria, conn); } else { tempResults = peer.doSelect((org.apache.torque.criteria.Criteria) criteria, conn); } if (tempResults.size() < criteria.getLimit()) { allRecordsRetrieved = true; } synchronized (results) { results.addAll(tempResults); } currentlyFilledTo += tempResults.size(); boolean perhapsLastPage = true; // If the extra record was indeed found then we know we are not // on the last page but we must now get rid of it. if (results.size() == memoryLimit + 1) { synchronized (results) { results.remove(currentlyFilledTo--); } perhapsLastPage = false; } if (results.size() > 0 && blockBegin + currentlyFilledTo >= totalRecords) { // Add 1 because index starts at 0 totalRecords = blockBegin + currentlyFilledTo + 1; } // if the db has limited the datasets, we must retrieve all // datasets. if (allRecordsRetrieved) { queryCompleted = true; // The following ugly condition ensures that the totals are // not finalized when a user does something like requesting // a page greater than what exists in the database. if (perhapsLastPage && getCurrentPageNumber() <= getTotalPages()) { totalsFinalized = true; } } } Transaction.commit(conn); conn = null; if (log.isDebugEnabled()) { log.debug("run(): While loop terminated because either:"); log.debug("run(): 1. qds.allRecordsRetrieved(): " + allRecordsRetrieved); log.debug("run(): 2. killThread: " + killThread); log.debug("run(): 3. !(currentlyFilledTo + size <= blockEnd): !" + (currentlyFilledTo + pageSize <= blockEnd)); log.debug("run(): - currentlyFilledTo: " + currentlyFilledTo); log.debug("run(): - size: " + pageSize); log.debug("run(): - blockEnd: " + blockEnd); log.debug("run(): - results.size(): " + results.size()); } } catch (Exception e) { log.error(e); } finally { if (conn != null) { Transaction.safeRollback(conn); } threadRunning = false; // Make sure getResults() finally returns if we die. queryCompleted = true; if (log.isDebugEnabled()) { log.debug("Exiting query thread"); } } } /** * Starts a new thread to retrieve the result set. * * @param initialSize the initial size for each block. */ private synchronized void startQuery(int initialSize) { if (log.isDebugEnabled()) { log.debug("Starting query thread"); } if (!threadRunning) { pageSize = initialSize; currentlyFilledTo = -1; queryCompleted = false; thread = new Thread(this); thread.setName("LargeSelect query Thread"); thread.start(); threadRunning = true; if (log.isDebugEnabled()) { log.debug("query thread started"); } } } /** * Used to stop filling the memory with the current block of results, if it * has been determined that they are no longer relevant. * * @throws TorqueException if a sleep is interrupted. */ private synchronized void stopQuery() throws TorqueException { if (log.isDebugEnabled()) { log.debug("stopQuery(): Stopping query thread"); } if (threadRunning) { killThread = true; while (thread.isAlive()) { try { Thread.sleep(QUERY_STOP_SLEEP_TIME); } catch (InterruptedException e) { throw new TorqueException("Unexpected interruption", e); } } killThread = false; if (log.isDebugEnabled()) { log.debug("stopQuery(): query thread stopped."); } } } /** * Retrieve the number of the current page. * * @return the current page number. */ public int getCurrentPageNumber() { return position / pageSize; } /** * Retrieve the total number of search result records that are known to * exist (this will be the actual value when the query has completeted (see * <code>getTotalsFinalized()</code>). The convenience method * <code>getRecordProgressText()</code> may be more useful for presenting to * users. * * @return the number of result records known to exist (not accurate until * <code>getTotalsFinalized()</code> returns <code>true</code>). */ public int getTotalRecords() { return totalRecords; } /** * Provide an indication of whether or not paging of results will be * required. * * @return <code>true</code> when multiple pages of results exist. */ public boolean getPaginated() { // Handle a page memory limit of 1 page. if (!getTotalsFinalized()) { return true; } return blockBegin + currentlyFilledTo + 1 > pageSize; } /** * Retrieve the total number of pages of search results that are known to * exist (this will be the actual value when the query has completeted (see * <code>getQyeryCompleted()</code>). The convenience method * <code>getPageProgressText()</code> may be more useful for presenting to * users. * * @return the number of pages of results known to exist (not accurate until * <code>getTotalsFinalized()</code> returns <code>true</code>). */ public int getTotalPages() { if (totalPages > -1) { return totalPages; } int tempPageCount = getTotalRecords() / pageSize + (getTotalRecords() % pageSize > 0 ? 1 : 0); if (getTotalsFinalized()) { totalPages = tempPageCount; } return tempPageCount; } /** * Retrieve the page size. * * @return the number of records returned on each invocation of * <code>getNextResults()</code>/<code>getPreviousResults()</code>. */ public int getPageSize() { return pageSize; } /** * Provide access to indicator that the total values for the number of * records and pages are now accurate as opposed to known upper limits. * * @return <code>true</code> when the totals are known to have been fully * computed. */ public boolean getTotalsFinalized() { return totalsFinalized; } /** * Provide a way of changing the more pages/records indicator. * * @param moreIndicator the indicator to use in place of the default * (">"). */ public void setMoreIndicator(String moreIndicator) { this.moreIndicator = moreIndicator; } /** * Retrieve the more pages/records indicator. */ public String getMoreIndicator() { return this.moreIndicator; } /** * Sets the multiplier that will be used to compute the memory limit when a * constructor with no memory page limit is used - the memory limit will be * this number multiplied by the page size. * * @param memoryPageLimit the maximum number of pages to be in memory * at one time. */ public void setMemoryPageLimit(int memoryPageLimit) { this.memoryPageLimit = memoryPageLimit; } /** * Retrieves the multiplier that will be used to compute the memory limit * when a constructor with no memory page limit is used - the memory limit * will be this number multiplied by the page size. */ public int getMemoryPageLimit() { return this.memoryPageLimit; } /** * A convenience method that provides text showing progress through the * selected rows on a page basis. * * @return progress text in the form of "1 of > 5" where ">" can be * configured using <code>setMoreIndicator()</code>. */ public String getPageProgressText() { StringBuffer result = new StringBuffer(); result.append(getCurrentPageNumber()); result.append(" of "); if (!totalsFinalized) { result.append(moreIndicator); result.append(" "); } result.append(getTotalPages()); return result.toString(); } /** * Provides a count of the number of rows to be displayed on the current * page - for the last page this may be less than the configured page size. * * @return the number of records that are included on the current page of * results. * @throws TorqueException if invoking the <code>populateObjects()<code> * method runs into problems or a sleep is unexpectedly interrupted. */ public int getCurrentPageSize() throws TorqueException { if (null == getCurrentPageResults()) { return 0; } return getCurrentPageResults().size(); } /** * Provide the record number of the first row included on the current page. * * @return The record number of the first row of the current page. */ public int getFirstRecordNoForPage() { if (getCurrentPageNumber() < 1) { return 0; } return (getCurrentPageNumber() - 1) * getPageSize() + 1; } /** * Provide the record number of the last row included on the current page. * * @return the record number of the last row of the current page. * @throws TorqueException if invoking the <code>populateObjects()<code> * method runs into problems or a sleep is unexpectedly interrupted. */ public int getLastRecordNoForPage() throws TorqueException { if (0 == getCurrentPageNumber()) { return 0; } return (getCurrentPageNumber() - 1) * getPageSize() + getCurrentPageSize(); } /** * A convenience method that provides text showing progress through the * selected rows on a record basis. * * @return progress text in the form of "26 - 50 of > 250" where ">" * can be configured using <code>setMoreIndicator()</code>. * @throws TorqueException if invoking the <code>populateObjects()<code> * method runs into problems or a sleep is unexpectedly interrupted. */ public String getRecordProgressText() throws TorqueException { StringBuffer result = new StringBuffer(); result.append(getFirstRecordNoForPage()); result.append(" - "); result.append(getLastRecordNoForPage()); result.append(" of "); if (!totalsFinalized) { result.append(moreIndicator); result.append(" "); } result.append(getTotalRecords()); return result.toString(); } /** * Indicates if further result pages are available. * * @return <code>true</code> when further results are available. */ public boolean getNextResultsAvailable() { if (!totalsFinalized || getCurrentPageNumber() < getTotalPages()) { return true; } return false; } /** * Indicates if previous results pages are available. * * @return <code>true</code> when previous results are available. */ public boolean getPreviousResultsAvailable() { if (getCurrentPageNumber() <= 1) { return false; } return true; } /** * Indicates if any results are available. * * @return <code>true</code> of any results are available. */ public boolean hasResultsAvailable() { return getTotalRecords() > 0; } /** * Clear the query result so that the query is reexecuted when the next page * is retrieved. You may want to invoke this method if you are returning to * a page after performing an operation on an item in the result set. * * @throws TorqueException if a sleep is interrupted. */ public synchronized void invalidateResult() throws TorqueException { stopQuery(); blockBegin = 0; blockEnd = 0; currentlyFilledTo = -1; results = null; // TODO Perhaps store the oldPosition and immediately restart the // query. // oldPosition = position; position = 0; totalPages = -1; totalRecords = 0; queryCompleted = false; totalsFinalized = false; lastResults = null; } /** * Retrieve a search parameter. This acts as a convenient place to store * parameters that relate to the LargeSelect to make it easy to get at them * in order to repopulate search parameters on a form when the next page of * results is retrieved - they in no way effect the operation of * LargeSelect. * * @param name the search parameter key to retrieve. * @return the value of the search parameter. */ public String getSearchParam(String name) { return getSearchParam(name, null); } /** * Retrieve a search parameter. This acts as a convenient place to store * parameters that relate to the LargeSelect to make it easy to get at them * in order to repopulate search parameters on a form when the next page of * results is retrieved - they in no way effect the operation of * LargeSelect. * * @param name the search parameter key to retrieve. * @param defaultValue the default value to return if the key is not found. * @return the value of the search parameter. */ public String getSearchParam(String name, String defaultValue) { if (null == params) { return defaultValue; } String value = params.get(name); return null == value ? defaultValue : value; } /** * Set a search parameter. If the value is <code>null</code> then the * key will be removed from the parameters. * * @param name the search parameter key to set. * @param value the value of the search parameter to store. */ public void setSearchParam(String name, String value) { if (null == value) { removeSearchParam(name); } else { if (null != name) { if (null == params) { params = new Hashtable<String, String>(); } params.put(name, value); } } } /** * Remove a value from the search parameters. * * @param name the search parameter key to remove. */ public void removeSearchParam(String name) { if (null != params) { params.remove(name); } } /** * Deserialize this LargeSelect instance. * * @param inputStream The serialization input stream. * @throws IOException * @throws ClassNotFoundException */ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); // avoid NPE because of Tomcat de-serialization of sessions if (Torque.isInit()) { startQuery(pageSize); } } /** * Provide something useful for debugging purposes. * * @return some basic information about this instance of LargeSelect. */ @Override public String toString() { StringBuffer result = new StringBuffer(); result.append("LargeSelect - TotalRecords: "); result.append(getTotalRecords()); result.append(" TotalsFinalised: "); result.append(getTotalsFinalized()); result.append("\nParameters:"); if (null == params || params.size() == 0) { result.append(" No parameters have been set."); } else { Set<String> keys = params.keySet(); for (Iterator<String> iter = keys.iterator(); iter.hasNext();) { String key = iter.next(); String val = params.get(key); result.append("\n ").append(key).append(": ").append(val); } } return result.toString(); } }