Coverage report

  %line %branch
org.apache.torque.util.LargeSelect
0% 
0% 

 1  
 package org.apache.torque.util;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 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  
 /**
 43  
  * This class can be used to retrieve a large result set from a database query.
 44  
  * The query is started and then rows are returned a page at a time.  The <code>
 45  
  * LargeSelect</code> is meant to be placed into the Session or User.Temp, so
 46  
  * that it can be used in response to several related requests.  Note that in
 47  
  * order to use <code>LargeSelect</code> you need to be willing to accept the
 48  
  * fact that the result set may become inconsistent with the database if updates
 49  
  * are processed subsequent to the queries being executed.  Specifying a memory
 50  
  * page limit of 1 will give you a consistent view of the records but the totals
 51  
  * may not be accurate and the performance will be terrible.  In most cases
 52  
  * the potential for inconsistencies data should not cause any serious problems
 53  
  * and performance should be pretty good (but read on for further warnings).
 54  
  *
 55  
  * <p>The idea here is that the full query result would consume too much memory
 56  
  * and if displayed to a user the page would be too long to be useful.  Rather
 57  
  * than loading the full result set into memory, a window of data (the memory
 58  
  * limit) is loaded and retrieved a page at a time.  If a request occurs for
 59  
  * data that falls outside the currently loaded window of data then a new query
 60  
  * is executed to fetch the required data.  Performance is optimized by
 61  
  * starting a thread to execute the database query and fetch the results.  This
 62  
  * will perform best when paging forwards through the data, but a minor
 63  
  * optimization where the window is moved backwards by two rather than one page
 64  
  * is included for when a user pages past the beginning of the window.
 65  
  *
 66  
  * <p>As the query is performed in in steps, it is often the case that the total
 67  
  * number of records and pages of data is unknown.  <code>LargeSelect</code>
 68  
  * provides various methods for indicating how many records and pages it is
 69  
  * currently aware of and for presenting this information to users.
 70  
  *
 71  
  * <p><code>LargeSelect</code> utilises the <code>Criteria</code> methods
 72  
  * <code>setOffset()</code> and <code>setLimit()</code> to limit the amount of
 73  
  * data retrieved from the database - these values are either passed through to
 74  
  * the DBMS when supported (efficient with the caveat below) or handled by
 75  
  * the Village API when it is not (not so efficient).  At time of writing
 76  
  * <code>Criteria</code> will only pass the offset and limit through to MySQL
 77  
  * and PostgreSQL (with a few changes to <code>DBOracle</code> and <code>
 78  
  * BasePeer</code> Oracle support can be implemented by utilising the <code>
 79  
  * rownum</code> pseudo column).
 80  
  *
 81  
  * <p>As <code>LargeSelect</code> must re-execute the query each time the user
 82  
  * pages out of the window of loaded data, you should consider the impact of
 83  
  * non-index sort orderings and other criteria that will require the DBMS to
 84  
  * execute the entire query before filtering down to the offset and limit either
 85  
  * internally or via Village.
 86  
  *
 87  
  * <p>The memory limit defaults to 5 times the page size you specify, but
 88  
  * alternative constructors and the class method <code>setMemoryPageLimit()
 89  
  * </code> allow you to override this for a specific instance of
 90  
  * <code>LargeSelect</code> or future instances respectively.
 91  
  *
 92  
  * <p>Some of the constructors allow you to specify the name of the class to use
 93  
  * to build the returnd rows.  This works by using reflection to find <code>
 94  
  * addSelectColumns(Criteria)</code> and <code>populateObjects(List)</code>
 95  
  * methods to add the necessary select columns to the criteria (only if it
 96  
  * doesn't already contain any) and to convert query results from Village
 97  
  * <code>Record</code> objects to a class defined within the builder class.
 98  
  * This allows you to use any of the Torque generated Peer classes, but also
 99  
  * makes it fairly simple to construct business object classes that can be used
 100  
  * for this purpose (simply copy and customise the <code>addSelectColumns()
 101  
  * </code>, <code>populateObjects()</code>, <code>row2Object()</code> and <code>
 102  
  * populateObject()</code> methods from an existing Peer class).
 103  
  *
 104  
  * <p>Typically you will create a <code>LargeSelect</code> using your <code>
 105  
  * Criteria</code> (perhaps created from the results of a search parameter
 106  
  * page), page size, memory page limit and return class name (for which you may
 107  
  * have defined a business object class before hand) and place this in user.Temp
 108  
  * thus:
 109  
  *
 110  
  * <pre>
 111  
  *     data.getUser().setTemp("someName", largeSelect);
 112  
  * </pre>
 113  
  *
 114  
  * <p>In your template you will then use something along the lines of:
 115  
  *
 116  
  * <pre>
 117  
  *    #set($largeSelect = $data.User.getTemp("someName"))
 118  
  *    #set($searchop = $data.Parameters.getString("searchop"))
 119  
  *    #if($searchop.equals("prev"))
 120  
  *      #set($recs = $largeSelect.PreviousResults)
 121  
  *    #else
 122  
  *      #if($searchop.equals("goto"))
 123  
  *        #set($recs = $largeSelect.getPage($data.Parameters.getInt("page", 1)))
 124  
  *      #else
 125  
  *        #set($recs = $largeSelect.NextResults)
 126  
  *      #end
 127  
  *    #end
 128  
  * </pre>
 129  
  *
 130  
  * <p>...to move through the records.  <code>LargeSelect</code> implements a
 131  
  * number of convenience methods that make it easy to add all of the necessary
 132  
  * bells and whistles to your template.
 133  
  *
 134  
  * @author <a href="mailto:john.mcnally@clearink.com">John D. McNally</a>
 135  
  * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
 136  
  * @version $Id: LargeSelect.java 534001 2007-05-01 10:46:05Z tv $
 137  
  */
 138  
 public class LargeSelect implements Runnable, Serializable
 139  
 {
 140  
     /** Serial version */
 141  
     private static final long serialVersionUID = -1166842932571491942L;
 142  
 
 143  
     /** The number of records that a page consists of.  */
 144  
     private int pageSize;
 145  
     /** The maximum number of records to maintain in memory. */
 146  
     private int memoryLimit;
 147  
 
 148  
     /** The record number of the first record in memory. */
 149  0
     private transient int blockBegin = 0;
 150  
     /** The record number of the last record in memory. */
 151  
     private transient int blockEnd;
 152  
     /** How much of the memory block is currently occupied with result data. */
 153  0
     private volatile int currentlyFilledTo = -1;
 154  
 
 155  
     /** The SQL query that this <code>LargeSelect</code> represents. */
 156  
     private String query;
 157  
     /** The database name to get from Torque. */
 158  
     private String dbName;
 159  
 
 160  
     /** The memory store of records. */
 161  0
     private transient List results = null;
 162  
 
 163  
     /** The thread that executes the query. */
 164  0
     private transient Thread thread = null;
 165  
     /**
 166  
      * A flag used to kill the thread when the currently executing query is no
 167  
      * longer required.
 168  
      */
 169  0
     private transient volatile boolean killThread = false;
 170  
     /** A flag that indicates whether or not the query thread is running. */
 171  0
     private transient volatile boolean threadRunning = false;
 172  
     /**
 173  
      * An indication of whether or not the current query has completed
 174  
      * processing.
 175  
      */
 176  0
     private transient volatile boolean queryCompleted = false;
 177  
     /**
 178  
      * An indication of whether or not the totals (records and pages) are at
 179  
      * their final values.
 180  
      */
 181  0
     private transient boolean totalsFinalized = false;
 182  
 
 183  
     /** The cursor position in the result set. */
 184  
     private int position;
 185  
     /** The total number of pages known to exist. */
 186  0
     private int totalPages = -1;
 187  
     /** The total number of records known to exist. */
 188  0
     private int totalRecords = 0;
 189  
 
 190  
     /** The criteria used for the query. */
 191  0
     private Criteria criteria = null;
 192  
     /** The last page of results that were returned. */
 193  
     private transient List lastResults;
 194  
 
 195  
     /**
 196  
      * The class that is possibly used to construct the criteria and used
 197  
      * to transform the Village Records into the desired OM or business objects.
 198  
      */
 199  0
     private Class returnBuilderClass = null;
 200  
     /**
 201  
      * A reference to the method in the return builder class that will
 202  
      * convert the Village Records to the desired class.
 203  
      */
 204  0
     private transient Method populateObjectsMethod = null;
 205  
 
 206  
     /**
 207  
      * The default value ("&gt;") used to indicate that the total number of
 208  
      * records or pages is unknown.
 209  
      */
 210  
     public static final String DEFAULT_MORE_INDICATOR = "&gt;";
 211  
 
 212  
     /**
 213  
      * The value used to indicate that the total number of records or pages is
 214  
      * unknown (default: "&gt;"). You can use <code>setMoreIndicator()</code>
 215  
      * to change this to whatever value you like (e.g. "more than").
 216  
      */
 217  0
     private static String moreIndicator = DEFAULT_MORE_INDICATOR;
 218  
 
 219  
     /**
 220  
      * The default value for the maximum number of pages of data to be retained
 221  
      * in memory.
 222  
      */
 223  
     public static final int DEFAULT_MEMORY_LIMIT_PAGES = 5;
 224  
 
 225  
     /**
 226  
      * The maximum number of pages of data to be retained in memory.  Use
 227  
      * <code>setMemoryPageLimit()</code> to provide your own value.
 228  
      */
 229  0
     private static int memoryPageLimit = DEFAULT_MEMORY_LIMIT_PAGES;
 230  
 
 231  
     /**
 232  
      * The number of milliseconds to sleep when the result of a query
 233  
      * is not yet available.
 234  
      */
 235  
     private static final int QUERY_NOT_COMPLETED_SLEEP_TIME = 500;
 236  
 
 237  
     /**
 238  
      * The number of milliseconds to sleep before retrying to stop a query.
 239  
      */
 240  
     private static final int QUERY_STOP_SLEEP_TIME = 100;
 241  
 
 242  
     /** A place to store search parameters that relate to this query. */
 243  0
     private Hashtable params = null;
 244  
 
 245  
     /** Logging */
 246  0
     private static Log log = LogFactory.getLog(LargeSelect.class);
 247  
 
 248  
     /**
 249  
      * Creates a LargeSelect whose results are returned as a <code>List</code>
 250  
      * containing a maximum of <code>pageSize</code> Village <code>Record</code>
 251  
      * objects at a time, maintaining a maximum of
 252  
      * <code>LargeSelect.memoryPageLimit</code> pages of results in memory.
 253  
      *
 254  
      * @param criteria object used by BasePeer to build the query.  In order to
 255  
      * allow this class to utilise database server implemented offsets and
 256  
      * limits (when available), the provided criteria must not have any limit or
 257  
      * offset defined.
 258  
      * @param pageSize number of rows to return in one block.
 259  
      * @throws IllegalArgumentException if <code>criteria</code> uses one or
 260  
      * both of offset and limit, or if <code>pageSize</code> is less than 1;
 261  
      */
 262  
     public LargeSelect(Criteria criteria, int pageSize)
 263  
     {
 264  0
         this(criteria, pageSize, LargeSelect.memoryPageLimit);
 265  0
     }
 266  
 
 267  
     /**
 268  
      * Creates a LargeSelect whose results are returned as a <code>List</code>
 269  
      * containing a maximum of <code>pageSize</code> Village <code>Record</code>
 270  
      * objects at a time, maintaining a maximum of <code>memoryPageLimit</code>
 271  
      * pages of results in memory.
 272  
      *
 273  
      * @param criteria object used by BasePeer to build the query.  In order to
 274  
      * allow this class to utilise database server implemented offsets and
 275  
      * limits (when available), the provided criteria must not have any limit or
 276  
      * offset defined.
 277  
      * @param pageSize number of rows to return in one block.
 278  
      * @param memoryPageLimit maximum number of pages worth of rows to be held
 279  
      * in memory at one time.
 280  
      * @throws IllegalArgumentException if <code>criteria</code> uses one or
 281  
      * both of offset and limit, or if <code>pageSize</code> or
 282  
      * <code>memoryLimitPages</code> are less than 1;
 283  
      */
 284  
     public LargeSelect(Criteria criteria, int pageSize, class="keyword">int memoryPageLimit)
 285  0
     {
 286  0
         init(criteria, pageSize, memoryPageLimit);
 287  0
     }
 288  
 
 289  
     /**
 290  
      * Creates a LargeSelect whose results are returned as a <code>List</code>
 291  
      * containing a maximum of <code>pageSize</code> objects of the type
 292  
      * defined within the class named <code>returnBuilderClassName</code> at a
 293  
      * time, maintaining a maximum of <code>LargeSelect.memoryPageLimit</code>
 294  
      * pages of results in memory.
 295  
      *
 296  
      * @param criteria object used by BasePeer to build the query.  In order to
 297  
      * allow this class to utilise database server implemented offsets and
 298  
      * limits (when available), the provided criteria must not have any limit or
 299  
      * offset defined.  If the criteria does not include the definition of any
 300  
      * select columns the <code>addSelectColumns(Criteria)</code> method of
 301  
      * the class named as <code>returnBuilderClassName</code> will be used to
 302  
      * add them.
 303  
      * @param pageSize number of rows to return in one block.
 304  
      * @param returnBuilderClassName The name of the class that will be used to
 305  
      * build the result records (may implement <code>addSelectColumns(Criteria)
 306  
      * </code> and must implement <code>populateObjects(List)</code>).
 307  
      * @throws IllegalArgumentException if <code>criteria</code> uses one or
 308  
      * both of offset and limit, if <code>pageSize</code> is less than 1, or if
 309  
      * problems are experienced locating and invoking either one or both of
 310  
      * <code>addSelectColumns(Criteria)</code> and <code> populateObjects(List)
 311  
      * </code> in the class named <code>returnBuilderClassName</code>.
 312  
      */
 313  
     public LargeSelect(
 314  
             Criteria criteria,
 315  
             int pageSize,
 316  
             String returnBuilderClassName)
 317  
     {
 318  0
         this(
 319  
             criteria,
 320  
             pageSize,
 321  
             LargeSelect.memoryPageLimit,
 322  
             returnBuilderClassName);
 323  0
     }
 324  
 
 325  
     /**
 326  
      * Creates a LargeSelect whose results are returned as a <code>List</code>
 327  
      * containing a maximum of <code>pageSize</code> objects of the type
 328  
      * defined within the class named <code>returnBuilderClassName</code> at a
 329  
      * time, maintaining a maximum of <code>memoryPageLimit</code> pages of
 330  
      * results in memory.
 331  
      *
 332  
      * @param criteria object used by BasePeer to build the query.  In order to
 333  
      * allow this class to utilise database server implemented offsets and
 334  
      * limits (when available), the provided criteria must not have any limit or
 335  
      * offset defined.  If the criteria does not include the definition of any
 336  
      * select columns the <code>addSelectColumns(Criteria)</code> method of
 337  
      * the class named as <code>returnBuilderClassName</code> will be used to
 338  
      * add them.
 339  
      * @param pageSize number of rows to return in one block.
 340  
      * @param memoryPageLimit maximum number of pages worth of rows to be held
 341  
      * in memory at one time.
 342  
      * @param returnBuilderClassName The name of the class that will be used to
 343  
      * build the result records (may implement <code>addSelectColumns(Criteria)
 344  
      * </code> and must implement <code>populateObjects(List)</code>).
 345  
      * @throws IllegalArgumentException if <code>criteria</code> uses one or
 346  
      * both of offset and limit, if <code>pageSize</code> or <code>
 347  
      * memoryLimitPages</code> are less than 1, or if problems are experienced
 348  
      * locating and invoking either one or both of <code>
 349  
      * addSelectColumns(Criteria)</code> and <code> populateObjects(List)</code>
 350  
      * in the class named <code>returnBuilderClassName</code>.
 351  
      */
 352  
     public LargeSelect(
 353  
             Criteria criteria,
 354  
             int pageSize,
 355  
             int memoryPageLimit,
 356  
             String returnBuilderClassName)
 357  0
     {
 358  
         try
 359  
         {
 360  0
             this.returnBuilderClass = Class.forName(returnBuilderClassName);
 361  
 
 362  
             // Add the select columns if necessary.
 363  0
             if (criteria.getSelectColumns().size() == 0)
 364  
             {
 365  0
                 Class[] argTypes = { Criteria.class };
 366  0
                 Method selectColumnAdder =
 367  
                     returnBuilderClass.getMethod("addSelectColumns", argTypes);
 368  0
                 Object[] theArgs = { criteria };
 369  0
                 selectColumnAdder.invoke(returnBuilderClass.newInstance(),
 370  
                         theArgs);
 371  
             }
 372  
         }
 373  0
         catch (Exception e)
 374  
         {
 375  0
             throw new IllegalArgumentException(
 376  
                     "The class named as returnBuilderClassName does not "
 377  
                     + "provide the necessary facilities - see javadoc.");
 378  0
         }
 379  
 
 380  0
         init(criteria, pageSize, memoryPageLimit);
 381  0
     }
 382  
 
 383  
     /**
 384  
      * Access the populateObjects method.
 385  
      *
 386  
      * @throws SecurityException if the security manager does not allow
 387  
      *         access to the method.
 388  
      * @throws NoSuchMethodException if the poulateObjects method does not
 389  
      *         exist.
 390  
      */
 391  
     private Method getPopulateObjectsMethod()
 392  
             throws NoSuchMethodException
 393  
     {
 394  0
         if (null == populateObjectsMethod)
 395  
         {
 396  0
             Class[] argTypes = { List.class };
 397  0
             populateObjectsMethod
 398  
                     = returnBuilderClass.getMethod("populateObjects", argTypes);
 399  
         }
 400  0
         return populateObjectsMethod;
 401  
     }
 402  
 
 403  
     /**
 404  
      * Called by the constructors to start the query.
 405  
      *
 406  
      * @param criteria Object used by <code>BasePeer</code> to build the query.
 407  
      * In order to allow this class to utilise database server implemented
 408  
      * offsets and limits (when available), the provided criteria must not have
 409  
      * any limit or offset defined.
 410  
      * @param pageSize number of rows to return in one block.
 411  
      * @param memoryLimitPages maximum number of pages worth of rows to be held
 412  
      * in memory at one time.
 413  
      * @throws IllegalArgumentException if <code>criteria</code> uses one or
 414  
      * both of offset and limit and if <code>pageSize</code> or
 415  
      * <code>memoryLimitPages</code> are less than 1;
 416  
      */
 417  
     private void init(Criteria criteria, int pageSize, class="keyword">int memoryLimitPages)
 418  
     {
 419  0
         if (criteria.getOffset() != 0 || criteria.getLimit() != -1)
 420  
         {
 421  0
             throw new IllegalArgumentException(
 422  
                     "criteria must not use Offset and/or Limit.");
 423  
         }
 424  
 
 425  0
         if (pageSize < 1)
 426  
         {
 427  0
             throw new IllegalArgumentException(
 428  
                     "pageSize must be greater than zero.");
 429  
         }
 430  
 
 431  0
         if (memoryLimitPages < 1)
 432  
         {
 433  0
             throw new IllegalArgumentException(
 434  
                     "memoryPageLimit must be greater than zero.");
 435  
         }
 436  
 
 437  0
         this.pageSize = pageSize;
 438  0
         this.memoryLimit = pageSize * memoryLimitPages;
 439  0
         this.criteria = criteria;
 440  0
         dbName = criteria.getDbName();
 441  0
         blockEnd = blockBegin + memoryLimit - 1;
 442  0
         startQuery(pageSize);
 443  0
     }
 444  
 
 445  
     /**
 446  
      * Retrieve a specific page, if it exists.
 447  
      *
 448  
      * @param pageNumber the number of the page to be retrieved - must be
 449  
      * greater than zero.  An empty <code>List</code> will be returned if
 450  
      * <code>pageNumber</code> exceeds the total number of pages that exist.
 451  
      * @return a <code>List</code> of query results containing a maximum of
 452  
      * <code>pageSize</code> results.
 453  
      * @throws IllegalArgumentException when <code>pageNo</code> is not
 454  
      * greater than zero.
 455  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 456  
      * method runs into problems or a sleep is unexpectedly interrupted.
 457  
      */
 458  
     public List getPage(int pageNumber) throws TorqueException
 459  
     {
 460  0
         if (pageNumber < 1)
 461  
         {
 462  0
             throw new IllegalArgumentException(
 463  
                     "pageNumber must be greater than zero.");
 464  
         }
 465  0
         return getResults((pageNumber - 1) * pageSize);
 466  
     }
 467  
 
 468  
     /**
 469  
      * Gets the next page of rows.
 470  
      *
 471  
      * @return a <code>List</code> of query results containing a maximum of
 472  
      * <code>pageSize</code> reslts.
 473  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 474  
      * method runs into problems or a sleep is unexpectedly interrupted.
 475  
      */
 476  
     public List getNextResults() throws TorqueException
 477  
     {
 478  0
         if (!getNextResultsAvailable())
 479  
         {
 480  0
             return getCurrentPageResults();
 481  
         }
 482  0
         return getResults(position);
 483  
     }
 484  
 
 485  
     /**
 486  
      * Provide access to the results from the current page.
 487  
      *
 488  
      * @return a <code>List</code> of query results containing a maximum of
 489  
      * <code>pageSize</code> reslts.
 490  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 491  
      * method runs into problems or a sleep is unexpectedly interrupted.
 492  
      */
 493  
     public List getCurrentPageResults() throws TorqueException
 494  
     {
 495  0
         return null == lastResults && position > 0
 496  
                 ? getResults(position) : lastResults;
 497  
     }
 498  
 
 499  
     /**
 500  
      * Gets the previous page of rows.
 501  
      *
 502  
      * @return a <code>List</code> of query results containing a maximum of
 503  
      * <code>pageSize</code> reslts.
 504  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 505  
      * method runs into problems or a sleep is unexpectedly interrupted.
 506  
      */
 507  
     public List getPreviousResults() throws TorqueException
 508  
     {
 509  0
         if (!getPreviousResultsAvailable())
 510  
         {
 511  0
             return getCurrentPageResults();
 512  
         }
 513  
 
 514  
         int start;
 515  0
         if (position - 2 * pageSize < 0)
 516  
         {
 517  0
             start = 0;
 518  
         }
 519  
         else
 520  
         {
 521  0
             start = position - 2 * pageSize;
 522  
         }
 523  0
         return getResults(start);
 524  
     }
 525  
 
 526  
     /**
 527  
      * Gets a page of rows starting at a specified row.
 528  
      *
 529  
      * @param start the starting row.
 530  
      * @return a <code>List</code> of query results containing a maximum of
 531  
      * <code>pageSize</code> reslts.
 532  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 533  
      * method runs into problems or a sleep is unexpectedly interrupted.
 534  
      */
 535  
     private List getResults(int start) throws TorqueException
 536  
     {
 537  0
         return getResults(start, pageSize);
 538  
     }
 539  
 
 540  
     /**
 541  
      * Gets a block of rows starting at a specified row and containing a
 542  
      * specified number of rows.
 543  
      *
 544  
      * @param start the starting row.
 545  
      * @param size the number of rows.
 546  
      * @return a <code>List</code> of query results containing a maximum of
 547  
      * <code>pageSize</code> reslts.
 548  
      * @throws IllegalArgumentException if <code>size &gt; memoryLimit</code> or
 549  
      * <code>start</code> and <code>size</code> result in a situation that is
 550  
      * not catered for.
 551  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 552  
      * method runs into problems or a sleep is unexpectedly interrupted.
 553  
      */
 554  
     private synchronized List getResults(int start, class="keyword">int size)
 555  
             throws TorqueException
 556  
     {
 557  0
         if (log.isDebugEnabled())
 558  
         {
 559  0
             log.debug("getResults(start: " + start
 560  
                     + ", size: " + size + ") invoked.");
 561  
         }
 562  
 
 563  0
         if (size > memoryLimit)
 564  
         {
 565  0
             throw new IllegalArgumentException("size (" + size
 566  
                     + ") exceeds memory limit (" + memoryLimit + ").");
 567  
         }
 568  
 
 569  
         // Request was for a block of rows which should be in progess.
 570  
         // If the rows have not yet been returned, wait for them to be
 571  
         // retrieved.
 572  0
         if (start >= blockBegin && (start + size - 1) <= blockEnd)
 573  
         {
 574  0
             if (log.isDebugEnabled())
 575  
             {
 576  0
                 log.debug("getResults(): Sleeping until "
 577  
                         + "start+size-1 (" + (start + size - 1)
 578  
                         + ") > currentlyFilledTo (" + currentlyFilledTo
 579  
                         + ") && !queryCompleted (!" + queryCompleted + ")");
 580  
             }
 581  0
             while (((start + size - 1) > currentlyFilledTo) && !queryCompleted)
 582  
             {
 583  
                 try
 584  
                 {
 585  0
                     Thread.sleep(QUERY_NOT_COMPLETED_SLEEP_TIME);
 586  
                 }
 587  0
                 catch (InterruptedException e)
 588  
                 {
 589  0
                     throw new TorqueException("Unexpected interruption", e);
 590  0
                 }
 591  
             }
 592  
         }
 593  
 
 594  
         // Going in reverse direction, trying to limit db hits so assume user
 595  
         // might want at least 2 sets of data.
 596  0
         else if (start < blockBegin && start >= 0)
 597  
         {
 598  0
             if (log.isDebugEnabled())
 599  
             {
 600  0
                 log.debug("getResults(): Paging backwards as start (" + start
 601  
                         + ") < blockBegin (" + blockBegin + ") && start >= 0");
 602  
             }
 603  0
             stopQuery();
 604  0
             if (memoryLimit >= 2 * size)
 605  
             {
 606  0
                 blockBegin = start - size;
 607  0
                 if (blockBegin < 0)
 608  
                 {
 609  0
                     blockBegin = 0;
 610  
                 }
 611  
             }
 612  
             else
 613  
             {
 614  0
                 blockBegin = start;
 615  
             }
 616  0
             blockEnd = blockBegin + memoryLimit - 1;
 617  0
             startQuery(size);
 618  
             // Re-invoke getResults() to provide the wait processing.
 619  0
             return getResults(start, size);
 620  
         }
 621  
 
 622  
         // Assume we are moving on, do not retrieve any records prior to start.
 623  0
         else if ((start + size - 1) > blockEnd)
 624  
         {
 625  0
             if (log.isDebugEnabled())
 626  
             {
 627  0
                 log.debug("getResults(): Paging past end of loaded data as "
 628  
                         + "start+size-1 (" + (start + size - 1)
 629  
                         + ") > blockEnd (" + blockEnd + ")");
 630  
             }
 631  0
             stopQuery();
 632  0
             blockBegin = start;
 633  0
             blockEnd = blockBegin + memoryLimit - 1;
 634  0
             startQuery(size);
 635  
             // Re-invoke getResults() to provide the wait processing.
 636  0
             return getResults(start, size);
 637  
         }
 638  
 
 639  
         else
 640  
         {
 641  0
             throw new IllegalArgumentException("Parameter configuration not "
 642  
                     + "accounted for.");
 643  
         }
 644  
 
 645  0
         int fromIndex = start - blockBegin;
 646  0
         int toIndex = fromIndex + Math.min(size, results.size() - fromIndex);
 647  
 
 648  0
         if (log.isDebugEnabled())
 649  
         {
 650  0
             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  0
         synchronized (results)
 659  
         {
 660  0
             returnResults = new ArrayList(results.subList(fromIndex, toIndex));
 661  0
         }
 662  
 
 663  0
         if (null != returnBuilderClass)
 664  
         {
 665  
             // Invoke the populateObjects() method
 666  0
             Object[] theArgs = { returnResults };
 667  
             try
 668  
             {
 669  0
                 returnResults = (List) getPopulateObjectsMethod().invoke(
 670  
                         returnBuilderClass.newInstance(), theArgs);
 671  
             }
 672  0
             catch (Exception e)
 673  
             {
 674  0
                 throw new TorqueException("Unable to populate results", e);
 675  0
             }
 676  
         }
 677  0
         position = start + size;
 678  0
         lastResults = returnResults;
 679  0
         return class="keyword">returnResults;
 680  
     }
 681  
 
 682  
     /**
 683  
      * A background thread that retrieves the rows.
 684  
      */
 685  
     public void run()
 686  
     {
 687  
         boolean dbSupportsNativeLimit;
 688  
         boolean dbSupportsNativeOffset;
 689  
         try
 690  
         {
 691  0
             dbSupportsNativeLimit
 692  
                     = (Torque.getDB(dbName).supportsNativeLimit());
 693  0
             dbSupportsNativeOffset
 694  
                     = (Torque.getDB(dbName).supportsNativeOffset());
 695  
         }
 696  0
         catch (TorqueException e)
 697  
         {
 698  0
             log.error("run() : Exiting :", e);
 699  
             // we cannot execute further because Torque is not initialized
 700  
             // correctly
 701  0
             return;
 702  0
         }
 703  
 
 704  
         int size;
 705  0
         if (dbSupportsNativeLimit && dbSupportsNativeOffset)
 706  
         {
 707  
             // retrieve one page at a time
 708  0
             size = pageSize;
 709  
         }
 710  
         else
 711  
         {
 712  
             // retrieve the whole block at once and add the offset,
 713  
             // and add one record to check if we have reached the end of the
 714  
             // data
 715  0
             size = blockBegin + memoryLimit + 1;
 716  
         }
 717  
         /* The connection to the database. */
 718  0
         Connection conn = null;
 719  
         /** Used to retrieve query results from Village. */
 720  0
         QueryDataSet qds = null;
 721  
 
 722  
         try
 723  
         {
 724  
             // Add 1 to memory limit to check if the query ends on a page break.
 725  0
             results = new ArrayList(memoryLimit + 1);
 726  
 
 727  
             // Use the criteria to limit the rows that are retrieved to the
 728  
             // block of records that fit in the predefined memoryLimit.
 729  0
             if (dbSupportsNativeLimit)
 730  
             {
 731  0
                 if (dbSupportsNativeOffset)
 732  
                 {
 733  0
                     criteria.setOffset(blockBegin);
 734  
                     // Add 1 to memory limit to check if the query ends on a
 735  
                     // page break.
 736  0
                     criteria.setLimit(memoryLimit + 1);
 737  
                 }
 738  
                 else
 739  
                 {
 740  0
                     criteria.setLimit(blockBegin + memoryLimit + 1);
 741  
                 }
 742  
             }
 743  
 
 744  
             /* 
 745  
              * Fix criterions relating to booleanint or booleanchar columns
 746  
              * The defaultTableMap parameter in this call is null because we have
 747  
              * no default peer class inside LargeSelect. This means that all
 748  
              * columns not fully qualified will not be modified.
 749  
              */
 750  0
             BasePeer.correctBooleans(criteria, null);
 751  
             
 752  0
             query = BasePeer.createQueryString(criteria);
 753  
 
 754  
             // Get a connection to the db.
 755  0
             conn = Torque.getConnection(dbName);
 756  
 
 757  
             // Execute the query.
 758  0
             if (log.isDebugEnabled())
 759  
             {
 760  0
                 log.debug("run(): query = " + query);
 761  0
                 log.debug("run(): memoryLimit = " + memoryLimit);
 762  0
                 log.debug("run(): blockBegin = " + blockBegin);
 763  0
                 log.debug("run(): blockEnd = " + blockEnd);
 764  
             }
 765  0
             qds = new QueryDataSet(conn, query);
 766  
 
 767  
             // Continue getting rows one page at a time until the memory limit
 768  
             // is reached, all results have been retrieved, or the rest
 769  
             // of the results have been determined to be irrelevant.
 770  
             while (!killThread
 771  
                 && !qds.allRecordsRetrieved()
 772  0
                 && currentlyFilledTo + pageSize <= blockEnd)
 773  
             {
 774  
                 // This caters for when memoryLimit is not a multiple of
 775  
                 //  pageSize which it never is because we always add 1 above.
 776  
                 // not applicable if the db has no native limit where this
 777  
                 // was already considered
 778  0
                 if ((currentlyFilledTo + pageSize) >= blockEnd
 779  
                         && dbSupportsNativeLimit)
 780  
                 {
 781  
                     // Add 1 to check if the query ends on a page break.
 782  0
                     size = blockEnd - currentlyFilledTo + 1;
 783  
                 }
 784  
 
 785  0
                 if (log.isDebugEnabled())
 786  
                 {
 787  0
                     log.debug("run(): Invoking BasePeer.getSelectResults(qds, "
 788  
                             + size + ", false)");
 789  
                 }
 790  
 
 791  0
                 List tempResults
 792  
                         = BasePeer.getSelectResults(qds, size, false);
 793  
 
 794  0
                 int startIndex = dbSupportsNativeOffset ? 0 : blockBegin;
 795  
 
 796  0
                 synchronized (results)
 797  
                 {
 798  0
                     for (int i = startIndex, n = tempResults.size(); i < n; i++)
 799  
                     {
 800  0
                         results.add(tempResults.get(i));
 801  
                     }
 802  0
                 }
 803  
 
 804  0
                 if (dbSupportsNativeLimit && dbSupportsNativeOffset)
 805  
                 {
 806  0
                     currentlyFilledTo += tempResults.size();
 807  
                 }
 808  
                 else
 809  
                 {
 810  0
                     currentlyFilledTo = tempResults.size() - 1 - blockBegin;
 811  
                 }
 812  
 
 813  0
                 boolean perhapsLastPage = true;
 814  
 
 815  
                 // If the extra record was indeed found then we know we are not
 816  
                 // on the last page but we must now get rid of it.
 817  0
                 if ((dbSupportsNativeLimit
 818  
                         && (results.size() == memoryLimit + 1))
 819  
                     || (!dbSupportsNativeLimit
 820  
                             && currentlyFilledTo >= memoryLimit))
 821  
                 {
 822  0
                     synchronized (results)
 823  
                     {
 824  0
                         results.remove(currentlyFilledTo--);
 825  0
                     }
 826  0
                     perhapsLastPage = false;
 827  
                 }
 828  
 
 829  0
                 if (results.size() > 0
 830  
                     && blockBegin + currentlyFilledTo >= totalRecords)
 831  
                 {
 832  
                     // Add 1 because index starts at 0
 833  0
                     totalRecords = blockBegin + currentlyFilledTo + 1;
 834  
                 }
 835  
 
 836  
                 // if the db has limited the datasets, we must retrieve all
 837  
                 // datasets. If not, we are always finished because we fetch
 838  
                 // the whole block at once.
 839  0
                 if (qds.allRecordsRetrieved()
 840  
                         || !dbSupportsNativeLimit)
 841  
                 {
 842  0
                     queryCompleted = true;
 843  
                     // The following ugly condition ensures that the totals are
 844  
                     // not finalized when a user does something like requesting
 845  
                     // a page greater than what exists in the database.
 846  0
                     if (perhapsLastPage
 847  
                         && getCurrentPageNumber() <= getTotalPages())
 848  
                     {
 849  0
                         totalsFinalized = true;
 850  
                     }
 851  
                 }
 852  0
                 qds.clearRecords();
 853  
             }
 854  
 
 855  0
             if (log.isDebugEnabled())
 856  
             {
 857  0
                 log.debug("run(): While loop terminated because either:");
 858  0
                 log.debug("run(): 1. qds.allRecordsRetrieved(): "
 859  
                         + qds.allRecordsRetrieved());
 860  0
                 log.debug("run(): 2. killThread: " + killThread);
 861  0
                 log.debug("run(): 3. !(currentlyFilledTo + size <= blockEnd): !"
 862  
                         + (currentlyFilledTo + pageSize <= blockEnd));
 863  0
                 log.debug("run(): - currentlyFilledTo: " + currentlyFilledTo);
 864  0
                 log.debug("run(): - size: " + pageSize);
 865  0
                 log.debug("run(): - blockEnd: " + blockEnd);
 866  0
                 log.debug("run(): - results.size(): " + results.size());
 867  
             }
 868  0
         }
 869  0
         catch (TorqueException e)
 870  
         {
 871  0
             log.error(e);
 872  0
         }
 873  0
         catch (SQLException e)
 874  
         {
 875  0
             log.error(e);
 876  0
         }
 877  0
         catch (DataSetException e)
 878  
         {
 879  0
             log.error(e);
 880  0
         }
 881  
         finally
 882  
         {
 883  0
             try
 884  
             {
 885  0
                 if (qds != null)
 886  
                 {
 887  0
                     qds.close();
 888  
                 }
 889  0
                 Torque.closeConnection(conn);
 890  
             }
 891  0
             catch (SQLException e)
 892  
             {
 893  0
                 log.error(e);
 894  
             }
 895  0
             catch (DataSetException e)
 896  
             {
 897  0
                 log.error(e);
 898  0
             }
 899  0
             threadRunning = false;
 900  0
         }
 901  0
     }
 902  
 
 903  
     /**
 904  
      * Starts a new thread to retrieve the result set.
 905  
      *
 906  
      * @param initialSize the initial size for each block.
 907  
      */
 908  
     private synchronized void startQuery(int initialSize)
 909  
     {
 910  0
         if (!threadRunning)
 911  
         {
 912  0
             pageSize = initialSize;
 913  0
             currentlyFilledTo = -1;
 914  0
             queryCompleted = false;
 915  0
             thread = new Thread(this);
 916  0
             thread.start();
 917  0
             threadRunning = true;
 918  
         }
 919  0
     }
 920  
 
 921  
     /**
 922  
      * Used to stop filling the memory with the current block of results, if it
 923  
      * has been determined that they are no longer relevant.
 924  
      *
 925  
      * @throws TorqueException if a sleep is interrupted.
 926  
      */
 927  
     private synchronized void stopQuery() throws TorqueException
 928  
     {
 929  0
         if (threadRunning)
 930  
         {
 931  0
             killThread = true;
 932  0
             while (thread.isAlive())
 933  
             {
 934  
                 try
 935  
                 {
 936  0
                     Thread.sleep(QUERY_STOP_SLEEP_TIME);
 937  
                 }
 938  0
                 catch (InterruptedException e)
 939  
                 {
 940  0
                     throw new TorqueException("Unexpected interruption", e);
 941  0
                 }
 942  
             }
 943  0
             killThread = false;
 944  
         }
 945  0
     }
 946  
 
 947  
     /**
 948  
      * Retrieve the number of the current page.
 949  
      *
 950  
      * @return the current page number.
 951  
      */
 952  
     public int getCurrentPageNumber()
 953  
     {
 954  0
         return position / pageSize;
 955  
     }
 956  
 
 957  
     /**
 958  
      * Retrieve the total number of search result records that are known to
 959  
      * exist (this will be the actual value when the query has completeted (see
 960  
      * <code>getTotalsFinalized()</code>).  The convenience method
 961  
      * <code>getRecordProgressText()</code> may be more useful for presenting to
 962  
      * users.
 963  
      *
 964  
      * @return the number of result records known to exist (not accurate until
 965  
      * <code>getTotalsFinalized()</code> returns <code>true</code>).
 966  
      */
 967  
     public int getTotalRecords()
 968  
     {
 969  0
         return totalRecords;
 970  
     }
 971  
 
 972  
     /**
 973  
      * Provide an indication of whether or not paging of results will be
 974  
      * required.
 975  
      *
 976  
      * @return <code>true</code> when multiple pages of results exist.
 977  
      */
 978  
     public boolean getPaginated()
 979  
     {
 980  
         // Handle a page memory limit of 1 page.
 981  0
         if (!getTotalsFinalized())
 982  
         {
 983  0
             return true;
 984  
         }
 985  0
         return blockBegin + currentlyFilledTo + 1 > pageSize;
 986  
     }
 987  
 
 988  
     /**
 989  
      * Retrieve the total number of pages of search results that are known to
 990  
      * exist (this will be the actual value when the query has completeted (see
 991  
      * <code>getQyeryCompleted()</code>).  The convenience method
 992  
      * <code>getPageProgressText()</code> may be more useful for presenting to
 993  
      * users.
 994  
      *
 995  
      * @return the number of pages of results known to exist (not accurate until
 996  
      * <code>getTotalsFinalized()</code> returns <code>true</code>).
 997  
      */
 998  
     public int getTotalPages()
 999  
     {
 1000  0
         if (totalPages > -1)
 1001  
         {
 1002  0
             return totalPages;
 1003  
         }
 1004  
 
 1005  0
         int tempPageCount =  getTotalRecords() / pageSize
 1006  
                 + (getTotalRecords() % pageSize > 0 ? 1 : 0);
 1007  
 
 1008  0
         if (getTotalsFinalized())
 1009  
         {
 1010  0
             totalPages = tempPageCount;
 1011  
         }
 1012  
 
 1013  0
         return tempPageCount;
 1014  
     }
 1015  
 
 1016  
     /**
 1017  
      * Retrieve the page size.
 1018  
      *
 1019  
      * @return the number of records returned on each invocation of
 1020  
      * <code>getNextResults()</code>/<code>getPreviousResults()</code>.
 1021  
      */
 1022  
     public int getPageSize()
 1023  
     {
 1024  0
         return pageSize;
 1025  
     }
 1026  
 
 1027  
     /**
 1028  
      * Provide access to indicator that the total values for the number of
 1029  
      * records and pages are now accurate as opposed to known upper limits.
 1030  
      *
 1031  
      * @return <code>true</code> when the totals are known to have been fully
 1032  
      * computed.
 1033  
      */
 1034  
     public boolean getTotalsFinalized()
 1035  
     {
 1036  0
         return totalsFinalized;
 1037  
     }
 1038  
 
 1039  
     /**
 1040  
      * Provide a way of changing the more pages/records indicator.
 1041  
      *
 1042  
      * @param moreIndicator the indicator to use in place of the default
 1043  
      * ("&gt;").
 1044  
      */
 1045  
     public static void setMoreIndicator(String moreIndicator)
 1046  
     {
 1047  0
         LargeSelect.moreIndicator = moreIndicator;
 1048  0
     }
 1049  
 
 1050  
     /**
 1051  
      * Retrieve the more pages/records indicator.
 1052  
      */
 1053  
     public static String getMoreIndicator()
 1054  
     {
 1055  0
         return LargeSelect.moreIndicator;
 1056  
     }
 1057  
 
 1058  
     /**
 1059  
      * Sets the multiplier that will be used to compute the memory limit when a
 1060  
      * constructor with no memory page limit is used - the memory limit will be
 1061  
      * this number multiplied by the page size.
 1062  
      *
 1063  
      * @param memoryPageLimit the maximum number of pages to be in memory
 1064  
      * at one time.
 1065  
      */
 1066  
     public static void setMemoryPageLimit(int memoryPageLimit)
 1067  
     {
 1068  0
         LargeSelect.memoryPageLimit = memoryPageLimit;
 1069  0
     }
 1070  
 
 1071  
     /**
 1072  
      * Retrieves the multiplier that will be used to compute the memory limit
 1073  
      * when a constructor with no memory page limit is used - the memory limit
 1074  
      * will be this number multiplied by the page size.
 1075  
      */
 1076  
     public static int getMemoryPageLimit()
 1077  
     {
 1078  0
         return LargeSelect.memoryPageLimit;
 1079  
     }
 1080  
 
 1081  
     /**
 1082  
      * A convenience method that provides text showing progress through the
 1083  
      * selected rows on a page basis.
 1084  
      *
 1085  
      * @return progress text in the form of "1 of &gt; 5" where "&gt;" can be
 1086  
      * configured using <code>setMoreIndicator()</code>.
 1087  
      */
 1088  
     public String getPageProgressText()
 1089  
     {
 1090  0
         StringBuffer result = new StringBuffer();
 1091  0
         result.append(getCurrentPageNumber());
 1092  0
         result.append(" of ");
 1093  0
         if (!totalsFinalized)
 1094  
         {
 1095  0
             result.append(moreIndicator);
 1096  0
             result.append(" ");
 1097  
         }
 1098  0
         result.append(getTotalPages());
 1099  0
         return result.toString();
 1100  
     }
 1101  
 
 1102  
     /**
 1103  
      * Provides a count of the number of rows to be displayed on the current
 1104  
      * page - for the last page this may be less than the configured page size.
 1105  
      *
 1106  
      * @return the number of records that are included on the current page of
 1107  
      * results.
 1108  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 1109  
      * method runs into problems or a sleep is unexpectedly interrupted.
 1110  
      */
 1111  
     public int getCurrentPageSize() throws TorqueException
 1112  
     {
 1113  0
         if (null == getCurrentPageResults())
 1114  
         {
 1115  0
             return 0;
 1116  
         }
 1117  0
         return getCurrentPageResults().size();
 1118  
     }
 1119  
 
 1120  
     /**
 1121  
      * Provide the record number of the first row included on the current page.
 1122  
      *
 1123  
      * @return The record number of the first row of the current page.
 1124  
      */
 1125  
     public int getFirstRecordNoForPage()
 1126  
     {
 1127  0
         if (getCurrentPageNumber() < 1)
 1128  
         {
 1129  0
             return 0;
 1130  
         }
 1131  0
         return (getCurrentPageNumber() - 1) * getPageSize() + 1;
 1132  
     }
 1133  
 
 1134  
     /**
 1135  
      * Provide the record number of the last row included on the current page.
 1136  
      *
 1137  
      * @return the record number of the last row of the current page.
 1138  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 1139  
      * method runs into problems or a sleep is unexpectedly interrupted.
 1140  
      */
 1141  
     public int getLastRecordNoForPage() throws TorqueException
 1142  
     {
 1143  0
         if (0 == getCurrentPageNumber())
 1144  
         {
 1145  0
             return 0;
 1146  
         }
 1147  0
         return (getCurrentPageNumber() - 1) * getPageSize()
 1148  
                 + getCurrentPageSize();
 1149  
     }
 1150  
 
 1151  
     /**
 1152  
      * A convenience method that provides text showing progress through the
 1153  
      * selected rows on a record basis.
 1154  
      *
 1155  
      * @return progress text in the form of "26 - 50 of &gt; 250" where "&gt;"
 1156  
      * can be configured using <code>setMoreIndicator()</code>.
 1157  
      * @throws TorqueException if invoking the <code>populateObjects()<code>
 1158  
      * method runs into problems or a sleep is unexpectedly interrupted.
 1159  
      */
 1160  
     public String getRecordProgressText() throws TorqueException
 1161  
     {
 1162  0
         StringBuffer result = new StringBuffer();
 1163  0
         result.append(getFirstRecordNoForPage());
 1164  0
         result.append(" - ");
 1165  0
         result.append(getLastRecordNoForPage());
 1166  0
         result.append(" of ");
 1167  0
         if (!totalsFinalized)
 1168  
         {
 1169  0
             result.append(moreIndicator);
 1170  0
             result.append(" ");
 1171  
         }
 1172  0
         result.append(getTotalRecords());
 1173  0
         return result.toString();
 1174  
     }
 1175  
 
 1176  
     /**
 1177  
      * Indicates if further result pages are available.
 1178  
      *
 1179  
      * @return <code>true</code> when further results are available.
 1180  
      */
 1181  
     public boolean getNextResultsAvailable()
 1182  
     {
 1183  0
         if (!totalsFinalized || getCurrentPageNumber() < getTotalPages())
 1184  
         {
 1185  0
             return true;
 1186  
         }
 1187  0
         return false;
 1188  
     }
 1189  
 
 1190  
     /**
 1191  
      * Indicates if previous results pages are available.
 1192  
      *
 1193  
      * @return <code>true</code> when previous results are available.
 1194  
      */
 1195  
     public boolean getPreviousResultsAvailable()
 1196  
     {
 1197  0
         if (getCurrentPageNumber() <= 1)
 1198  
         {
 1199  0
             return false;
 1200  
         }
 1201  0
         return true;
 1202  
     }
 1203  
 
 1204  
     /**
 1205  
      * Indicates if any results are available.
 1206  
      *
 1207  
      * @return <code>true</code> of any results are available.
 1208  
      */
 1209  
     public boolean hasResultsAvailable()
 1210  
     {
 1211  0
         return getTotalRecords() > 0;
 1212  
     }
 1213  
 
 1214  
     /**
 1215  
      * Clear the query result so that the query is reexecuted when the next page
 1216  
      * is retrieved.  You may want to invoke this method if you are returning to
 1217  
      * a page after performing an operation on an item in the result set.
 1218  
      *
 1219  
      * @throws TorqueException if a sleep is interrupted.
 1220  
      */
 1221  
     public synchronized void invalidateResult() throws TorqueException
 1222  
     {
 1223  0
         stopQuery();
 1224  0
         blockBegin = 0;
 1225  0
         blockEnd = 0;
 1226  0
         currentlyFilledTo = -1;
 1227  0
         results = null;
 1228  
         // TODO Perhaps store the oldPosition and immediately restart the
 1229  
         // query.
 1230  
         // oldPosition = position;
 1231  0
         position = 0;
 1232  0
         totalPages = -1;
 1233  0
         totalRecords = 0;
 1234  0
         queryCompleted = false;
 1235  0
         totalsFinalized = false;
 1236  0
         lastResults = null;
 1237  0
     }
 1238  
 
 1239  
     /**
 1240  
      * Retrieve a search parameter.  This acts as a convenient place to store
 1241  
      * parameters that relate to the LargeSelect to make it easy to get at them
 1242  
      * in order to repopulate search parameters on a form when the next page of
 1243  
      * results is retrieved - they in no way effect the operation of
 1244  
      * LargeSelect.
 1245  
      *
 1246  
      * @param name the search parameter key to retrieve.
 1247  
      * @return the value of the search parameter.
 1248  
      */
 1249  
     public String getSearchParam(String name)
 1250  
     {
 1251  0
         return getSearchParam(name, null);
 1252  
     }
 1253  
 
 1254  
     /**
 1255  
      * Retrieve a search parameter.  This acts as a convenient place to store
 1256  
      * parameters that relate to the LargeSelect to make it easy to get at them
 1257  
      * in order to repopulate search parameters on a form when the next page of
 1258  
      * results is retrieved - they in no way effect the operation of
 1259  
      * LargeSelect.
 1260  
      *
 1261  
      * @param name the search parameter key to retrieve.
 1262  
      * @param defaultValue the default value to return if the key is not found.
 1263  
      * @return the value of the search parameter.
 1264  
      */
 1265  
     public String getSearchParam(String name, String defaultValue)
 1266  
     {
 1267  0
         if (null == params)
 1268  
         {
 1269  0
             return defaultValue;
 1270  
         }
 1271  0
         String value = (String) params.get(name);
 1272  0
         return null == value ? defaultValue : value;
 1273  
     }
 1274  
 
 1275  
     /**
 1276  
      * Set a search parameter.  If the value is <code>null</code> then the
 1277  
      * key will be removed from the parameters.
 1278  
      *
 1279  
      * @param name the search parameter key to set.
 1280  
      * @param value the value of the search parameter to store.
 1281  
      */
 1282  
     public void setSearchParam(String name, String value)
 1283  
     {
 1284  0
         if (null == value)
 1285  
         {
 1286  0
             removeSearchParam(name);
 1287  
         }
 1288  
         else
 1289  
         {
 1290  0
             if (null != name)
 1291  
             {
 1292  0
                 if (null == params)
 1293  
                 {
 1294  0
                     params = new Hashtable();
 1295  
                 }
 1296  0
                 params.put(name, value);
 1297  
             }
 1298  
         }
 1299  0
     }
 1300  
 
 1301  
     /**
 1302  
      * Remove a value from the search parameters.
 1303  
      *
 1304  
      * @param name the search parameter key to remove.
 1305  
      */
 1306  
     public void removeSearchParam(String name)
 1307  
     {
 1308  0
         if (null != params)
 1309  
         {
 1310  0
             params.remove(name);
 1311  
         }
 1312  0
     }
 1313  
 
 1314  
     /**
 1315  
      * Deserialize this LargeSelect instance.
 1316  
      *
 1317  
      * @param inputStream The serialization input stream.
 1318  
      * @throws IOException
 1319  
      * @throws ClassNotFoundException
 1320  
      */
 1321  
     private void readObject(ObjectInputStream inputStream)
 1322  
             throws IOException, ClassNotFoundException
 1323  
     {
 1324  0
         inputStream.defaultReadObject();
 1325  
 
 1326  
         // avoid NPE because of Tomcat de-serialization of sessions
 1327  0
         if (Torque.isInit())
 1328  
         {
 1329  0
             startQuery(pageSize);
 1330  
         }
 1331  0
     }
 1332  
 
 1333  
     /**
 1334  
      * Provide something useful for debugging purposes.
 1335  
      *
 1336  
      * @return some basic information about this instance of LargeSelect.
 1337  
      */
 1338  
     public String toString()
 1339  
     {
 1340  0
         StringBuffer result = new StringBuffer();
 1341  0
         result.append("LargeSelect - TotalRecords: ");
 1342  0
         result.append(getTotalRecords());
 1343  0
         result.append(" TotalsFinalised: ");
 1344  0
         result.append(getTotalsFinalized());
 1345  0
         result.append("\nParameters:");
 1346  0
         if (null == params || params.size() == 0)
 1347  
         {
 1348  0
             result.append(" No parameters have been set.");
 1349  
         }
 1350  
         else
 1351  
         {
 1352  0
             Set keys = params.keySet();
 1353  0
             for (Iterator iter = keys.iterator(); iter.hasNext();)
 1354  
             {
 1355  0
                 String key = (String) iter.next();
 1356  0
                 String val = (String) params.get(key);
 1357  0
                 result.append("\n ").append(key).append(": ").append(val);
 1358  
             }
 1359  
         }
 1360  0
         return result.toString();
 1361  
     }
 1362  
 
 1363  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.