Coverage report

  %line %branch
org.apache.torque.oid.IDBroker
0% 
0% 

 1  
 package org.apache.torque.oid;
 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.math.BigDecimal;
 23  
 import java.sql.Connection;
 24  
 import java.sql.ResultSet;
 25  
 import java.sql.Statement;
 26  
 import java.util.ArrayList;
 27  
 import java.util.Hashtable;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 
 31  
 import org.apache.commons.configuration.Configuration;
 32  
 import org.apache.commons.logging.Log;
 33  
 import org.apache.commons.logging.LogFactory;
 34  
 import org.apache.torque.Database;
 35  
 import org.apache.torque.Torque;
 36  
 import org.apache.torque.TorqueException;
 37  
 import org.apache.torque.map.TableMap;
 38  
 import org.apache.torque.util.Transaction;
 39  
 
 40  
 //!!
 41  
 // NOTE:
 42  
 // It would be nice to decouple this from
 43  
 // Torque. This is a great stand-alone utility.
 44  
 
 45  
 /**
 46  
  * This method of ID generation is used to ensure that code is
 47  
  * more database independent.  For example, MySQL has an auto-increment
 48  
  * feature while Oracle uses sequences.  It caches several ids to
 49  
  * avoid needing a Connection for every request.
 50  
  *
 51  
  * This class uses the table ID_TABLE defined in
 52  
  * conf/master/id-table-schema.xml.  The columns in ID_TABLE are used as
 53  
  * follows:<br>
 54  
  *
 55  
  * ID_TABLE_ID - The PK for this row (any unique int).<br>
 56  
  * TABLE_NAME - The name of the table you want ids for.<br>
 57  
  * NEXT_ID - The next id returned by IDBroker when it queries the
 58  
  *           database (not when it returns an id from memory).<br>
 59  
  * QUANTITY - The number of ids that IDBroker will cache in memory.<br>
 60  
  * <p>
 61  
  * Use this class like this:
 62  
  * <pre>
 63  
  * int id = dbMap.getIDBroker().getNextIdAsInt(null, "TABLE_NAME");
 64  
  *  - or -
 65  
  * BigDecimal[] ids = ((IDBroker)dbMap.getIDBroker())
 66  
  *     .getNextIds("TABLE_NAME", numOfIdsToReturn);
 67  
  * </pre>
 68  
  *
 69  
  * NOTE: When the ID_TABLE must be updated we must ensure that
 70  
  * IDBroker objects running in different JVMs do not overwrite each
 71  
  * other.  This is accomplished using using the transactional support
 72  
  * occuring in some databases.  Using this class with a database that
 73  
  * does not support transactions should be limited to a single JVM.
 74  
  *
 75  
  * @author <a href="mailto:frank.kim@clearink.com">Frank Y. Kim</a>
 76  
  * @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
 77  
  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
 78  
  * @version $Id: IDBroker.java 476550 2006-11-18 16:08:37Z tfischer $
 79  
  */
 80  
 public class IDBroker implements Runnable, IdGenerator
 81  
 {
 82  
     /** Name of the ID_TABLE = ID_TABLE */
 83  
     public static final String ID_TABLE = "ID_TABLE";
 84  
 
 85  
     /** Table_Name column name */
 86  
     public static final String COL_TABLE_NAME = "TABLE_NAME";
 87  
 
 88  
     /** Fully qualified Table_Name column name */
 89  
     public static final String TABLE_NAME = ID_TABLE + "." + COL_TABLE_NAME;
 90  
 
 91  
     /** ID column name */
 92  
     public static final String COL_TABLE_ID = "ID_TABLE_ID";
 93  
 
 94  
     /** Fully qualified ID column name */
 95  
     public static final String TABLE_ID = ID_TABLE + "." + COL_TABLE_ID;
 96  
 
 97  
     /** Next_ID column name */
 98  
     public static final String COL_NEXT_ID = "NEXT_ID";
 99  
 
 100  
     /** Fully qualified Next_ID column name */
 101  
     public static final String NEXT_ID = ID_TABLE + "." + COL_NEXT_ID;
 102  
 
 103  
     /** Quantity column name */
 104  
     public static final String COL_QUANTITY = "QUANTITY";
 105  
 
 106  
     /** Fully qualified Quantity column name */
 107  
     public static final String QUANTITY = ID_TABLE + "." + COL_QUANTITY;
 108  
 
 109  
     /** the name of the database in which this IdBroker is running. */
 110  
     private String databaseName;
 111  
 
 112  
     /**
 113  
      * The default size of the per-table meta data <code>Hashtable</code>
 114  
      * objects.
 115  
      */
 116  
     private static final int DEFAULT_SIZE = 40;
 117  
 
 118  
     /**
 119  
      * The cached IDs for each table.
 120  
      *
 121  
      * Key: String table name.
 122  
      * Value: List of Integer IDs.
 123  
      */
 124  0
     private Hashtable ids = new Hashtable(DEFAULT_SIZE);
 125  
 
 126  
     /**
 127  
      * The quantity of ids to grab for each table.
 128  
      *
 129  
      * Key: String table name.
 130  
      * Value: Integer quantity.
 131  
      */
 132  0
     private Hashtable quantityStore = new Hashtable(DEFAULT_SIZE);
 133  
 
 134  
     /**
 135  
      * The last time this IDBroker queried the database for ids.
 136  
      *
 137  
      * Key: String table name.
 138  
      * Value: Date of last id request.
 139  
      */
 140  0
     private Hashtable lastQueryTime = new Hashtable(DEFAULT_SIZE);
 141  
 
 142  
     /**
 143  
      * Amount of time for the thread to sleep
 144  
      */
 145  
     private static final int SLEEP_PERIOD = 60000;
 146  
 
 147  
     /**
 148  
      * The safety Margin
 149  
      */
 150  
     private static final float SAFETY_MARGIN = 1.2f;
 151  
 
 152  
     /**
 153  
      * The houseKeeperThread thread
 154  
      */
 155  0
     private Thread houseKeeperThread = null;
 156  
 
 157  
     /**
 158  
      * Are transactions supported?
 159  
      */
 160  0
     private boolean transactionsSupported = false;
 161  
 
 162  
     /**
 163  
      * The value of ONE!
 164  
      */
 165  0
     private static final BigDecimal ONE = new BigDecimal("1");
 166  
 
 167  
     /** the configuration */
 168  
     private Configuration configuration;
 169  
 
 170  
     /** property name */
 171  
     private static final String DB_IDBROKER_CLEVERQUANTITY =
 172  
         "idbroker.clever.quantity";
 173  
 
 174  
     /** property name */
 175  
     private static final String DB_IDBROKER_PREFETCH =
 176  
         "idbroker.prefetch";
 177  
 
 178  
     /** property name */
 179  
     private static final String DB_IDBROKER_USENEWCONNECTION =
 180  
         "idbroker.usenewconnection";
 181  
 
 182  
     /** the log */
 183  0
     private Log log = LogFactory.getLog(IDBroker.class);
 184  
 
 185  
     /**
 186  
      * constructs an IdBroker for the given Database.
 187  
      * @param database the database where this IdBroker is running in.
 188  
      */
 189  
     public IDBroker(Database database)
 190  
     {
 191  0
         this(database.getName());
 192  0
     }
 193  
 
 194  
     /**
 195  
      * Creates an IDBroker for the ID table.
 196  
      *
 197  
      * @param tMap A TableMap.
 198  
      * @deprecated Use IDBroker(DatabaseInfo) instead. Will be removed
 199  
      *             in a future version of Torque.
 200  
      */
 201  
     public IDBroker(TableMap tMap)
 202  
     {
 203  0
         this(tMap.getDatabaseMap().getName());
 204  0
     }
 205  
 
 206  
     /**
 207  
      * Constructor.
 208  
      * Provided as long as both Constructors, IDBroker(DatabaseInfo) and
 209  
      * IDBroker(TableMap), are around.
 210  
      * @param databaseName the name of the database for which this IdBroker
 211  
      *        provides Ids.
 212  
      */
 213  
     private IDBroker(String databaseName)
 214  0
     {
 215  0
         this.databaseName = databaseName;
 216  0
         configuration = Torque.getConfiguration();
 217  
 
 218  
         // Start the housekeeper thread only if prefetch has not been disabled
 219  0
         if (configuration.getBoolean(DB_IDBROKER_PREFETCH, true))
 220  
         {
 221  0
             houseKeeperThread = new Thread(this);
 222  
             // Indicate that this is a system thread. JVM will quit only when
 223  
             // there are no more active user threads. Settings threads spawned
 224  
             // internally by Torque as daemons allows commandline applications
 225  
             // using Torque terminate in an orderly manner.
 226  0
             houseKeeperThread.setDaemon(true);
 227  0
             houseKeeperThread.setName("Torque - ID Broker thread");
 228  0
             houseKeeperThread.start();
 229  
         }
 230  
 
 231  
         // Check for Transaction support.  Give warning message if
 232  
         // IDBroker is being used with a database that does not
 233  
         // support transactions.
 234  0
         Connection dbCon = null;
 235  
         try
 236  
         {
 237  0
             dbCon = Torque.getConnection(databaseName);
 238  
         }
 239  0
         catch (Throwable t)
 240  
         {
 241  0
             log.error("Could not open a connection to the database "
 242  
                     + databaseName,
 243  
                     t);
 244  0
             transactionsSupported = false;
 245  0
         }
 246  
         try
 247  
         {
 248  0
             transactionsSupported = dbCon.getMetaData().supportsTransactions();
 249  
         }
 250  0
         catch (Exception e)
 251  
         {
 252  0
             log.warn("Could not read from connection Metadata"
 253  
                     + " whether transactions are supported for the database "
 254  
                     + databaseName,
 255  
                     e);
 256  0
             transactionsSupported = false;
 257  
         }
 258  
         finally
 259  
         {
 260  0
             try
 261  
             {
 262  
                 // Return the connection to the pool.
 263  0
                 dbCon.close();
 264  
             }
 265  0
             catch (Exception e)
 266  
             {
 267  0
                 log.warn("Could not close the connection which was used "
 268  
                         + "for testing whether transactions are supported",
 269  
                         e);
 270  0
             }
 271  0
         }
 272  0
         if (!transactionsSupported)
 273  
         {
 274  0
             log.warn("IDBroker is being used with db '" + databaseName
 275  
                     + "', which does not support transactions. IDBroker "
 276  
                     + "attempts to use transactions to limit the possibility "
 277  
                     + "of duplicate key generation.  Without transactions, "
 278  
                     + "duplicate key generation is possible if multiple JVMs "
 279  
                     + "are used or other means are used to write to the "
 280  
                     + "database.");
 281  
         }
 282  0
     }
 283  
 
 284  
     /**
 285  
      * Set the configuration
 286  
      *
 287  
      * @param configuration the configuration
 288  
      */
 289  
     public void setConfiguration(Configuration configuration)
 290  
     {
 291  0
         this.configuration = configuration;
 292  0
     }
 293  
 
 294  
     /**
 295  
      * Returns an id as a primitive int.  Note this method does not
 296  
      * require a Connection, it just implements the KeyGenerator
 297  
      * interface.  if a Connection is needed one will be requested.
 298  
      * To force the use of the passed in connection set the configuration
 299  
      * property torque.idbroker.usenewconnection = false
 300  
      *
 301  
      * @param connection A Connection.
 302  
      * @param tableName an Object that contains additional info.
 303  
      * @return An int with the value for the id.
 304  
      * @exception Exception Database error.
 305  
      */
 306  
     public int getIdAsInt(Connection connection, Object tableName)
 307  
         throws Exception
 308  
     {
 309  0
         return getIdAsBigDecimal(connection, tableName).intValue();
 310  
     }
 311  
 
 312  
 
 313  
     /**
 314  
      * Returns an id as a primitive long. Note this method does not
 315  
      * require a Connection, it just implements the KeyGenerator
 316  
      * interface.  if a Connection is needed one will be requested.
 317  
      * To force the use of the passed in connection set the configuration
 318  
      * property torque.idbroker.usenewconnection = false
 319  
      *
 320  
      * @param connection A Connection.
 321  
      * @param tableName a String that identifies a table.
 322  
      * @return A long with the value for the id.
 323  
      * @exception Exception Database error.
 324  
      */
 325  
     public long getIdAsLong(Connection connection, Object tableName)
 326  
         throws Exception
 327  
     {
 328  0
         return getIdAsBigDecimal(connection, tableName).longValue();
 329  
     }
 330  
 
 331  
     /**
 332  
      * Returns an id as a BigDecimal. Note this method does not
 333  
      * require a Connection, it just implements the KeyGenerator
 334  
      * interface.  if a Connection is needed one will be requested.
 335  
      * To force the use of the passed in connection set the configuration
 336  
      * property torque.idbroker.usenewconnection = false
 337  
      *
 338  
      * @param connection A Connection.
 339  
      * @param tableName a String that identifies a table..
 340  
      * @return A BigDecimal id.
 341  
      * @exception Exception Database error.
 342  
      */
 343  
     public BigDecimal getIdAsBigDecimal(Connection connection,
 344  
                                         Object tableName)
 345  
         throws Exception
 346  
     {
 347  0
         BigDecimal[] id = getNextIds((String) tableName, 1, connection);
 348  0
         return id[0];
 349  
     }
 350  
 
 351  
     /**
 352  
      * Returns an id as a String. Note this method does not
 353  
      * require a Connection, it just implements the KeyGenerator
 354  
      * interface.  if a Connection is needed one will be requested.
 355  
      * To force the use of the passed in connection set the configuration
 356  
      * property torque.idbroker.usenewconnection = false
 357  
      *
 358  
      * @param connection A Connection should be null.
 359  
      * @param tableName a String that identifies a table.
 360  
      * @return A String id
 361  
      * @exception Exception Database error.
 362  
      */
 363  
     public String getIdAsString(Connection connection, Object tableName)
 364  
         throws Exception
 365  
     {
 366  0
         return getIdAsBigDecimal(connection, tableName).toString();
 367  
     }
 368  
 
 369  
 
 370  
     /**
 371  
      * A flag to determine the timing of the id generation     *
 372  
      * @return a <code>boolean</code> value
 373  
      */
 374  
     public boolean isPriorToInsert()
 375  
     {
 376  0
         return true;
 377  
     }
 378  
 
 379  
     /**
 380  
      * A flag to determine the timing of the id generation
 381  
      *
 382  
      * @return a <code>boolean</code> value
 383  
      */
 384  
     public boolean isPostInsert()
 385  
     {
 386  0
         return false;
 387  
     }
 388  
 
 389  
     /**
 390  
      * A flag to determine whether a Connection is required to
 391  
      * generate an id.
 392  
      *
 393  
      * @return a <code>boolean</code> value
 394  
      */
 395  
     public boolean isConnectionRequired()
 396  
     {
 397  0
         return false;
 398  
     }
 399  
 
 400  
     /**
 401  
      * This method returns x number of ids for the given table.
 402  
      *
 403  
      * @param tableName The name of the table for which we want an id.
 404  
      * @param numOfIdsToReturn The desired number of ids.
 405  
      * @return A BigDecimal.
 406  
      * @exception Exception Database error.
 407  
      */
 408  
     public synchronized BigDecimal[] getNextIds(String tableName,
 409  
                                                 int numOfIdsToReturn)
 410  
         throws Exception
 411  
     {
 412  0
         return getNextIds(tableName, numOfIdsToReturn, null);
 413  
     }
 414  
 
 415  
     /**
 416  
      * This method returns x number of ids for the given table.
 417  
      * Note this method does not require a Connection.
 418  
      * If a Connection is needed one will be requested.
 419  
      * To force the use of the passed in connection set the configuration
 420  
      * property torque.idbroker.usenewconnection = false
 421  
      *
 422  
      * @param tableName The name of the table for which we want an id.
 423  
      * @param numOfIdsToReturn The desired number of ids.
 424  
      * @param connection A Connection.
 425  
      * @return A BigDecimal.
 426  
      * @exception Exception Database error.
 427  
      */
 428  
     public synchronized BigDecimal[] getNextIds(String tableName,
 429  
                                                 int numOfIdsToReturn,
 430  
                                                 Connection connection)
 431  
         throws Exception
 432  
     {
 433  0
         if (tableName == null)
 434  
         {
 435  0
             throw new Exception("getNextIds(): tableName == null");
 436  
         }
 437  
 
 438  
         // A note about the synchronization:  I (jmcnally) looked at
 439  
         // the synchronized blocks to avoid thread issues that were
 440  
         // being used in this and the storeId method.  I do not think
 441  
         // they were being effective, so I synchronized the method.
 442  
         // I have left the blocks that did exist commented in the code
 443  
         // to make it easier for others to take a look, because it
 444  
         // would be preferrable to avoid the synchronization on the
 445  
         // method
 446  
 
 447  0
         List availableIds = (List) ids.get(tableName);
 448  
 
 449  0
         if (availableIds == null || availableIds.size() < numOfIdsToReturn)
 450  
         {
 451  0
             if (availableIds == null)
 452  
             {
 453  0
                 log.debug("Forced id retrieval - no available list");
 454  0
             }
 455  
             else
 456  
             {
 457  0
                 log.debug("Forced id retrieval - " + availableIds.size());
 458  
             }
 459  0
             storeIDs(tableName, true, connection);
 460  0
             availableIds = (List) ids.get(tableName);
 461  
         }
 462  
 
 463  0
         int size = availableIds.size() < numOfIdsToReturn
 464  
                 ? availableIds.size() : numOfIdsToReturn;
 465  
 
 466  0
         BigDecimal[] results = new BigDecimal[size];
 467  
 
 468  
         // We assume that availableIds will always come from the ids
 469  
         // Hashtable and would therefore always be the same object for
 470  
         // a specific table.
 471  
         //        synchronized (availableIds)
 472  
         //        {
 473  0
         for (int i = size - 1; i >= 0; i--)
 474  
         {
 475  0
             results[i] = (BigDecimal) availableIds.get(i);
 476  0
             availableIds.remove(i);
 477  
         }
 478  
         //        }
 479  
 
 480  0
         return results;
 481  
     }
 482  
 
 483  
     /**
 484  
      * @param tableName a <code>String</code> value that is used to identify
 485  
      * the row
 486  
      * @return a <code>boolean</code> value
 487  
      * @exception TorqueException if a Torque error occurs.
 488  
      * @exception Exception if another error occurs.
 489  
      */
 490  
     public boolean exists(String tableName)
 491  
         throws Exception
 492  
     {
 493  0
         String query = new StringBuffer(100)
 494  
             .append("select ")
 495  
             .append(TABLE_NAME)
 496  
             .append(" where ")
 497  
             .append(TABLE_NAME).append("='").append(tableName).append('\'')
 498  
             .toString();
 499  
 
 500  0
         boolean exists = false;
 501  0
         Connection dbCon = null;
 502  
         try
 503  
         {
 504  0
             dbCon = Torque.getConnection(databaseName);
 505  0
             Statement statement = dbCon.createStatement();
 506  0
             ResultSet rs = statement.executeQuery(query);
 507  0
             exists = rs.next();
 508  0
             statement.close();
 509  
         }
 510  
         finally
 511  
         {
 512  
             // Return the connection to the pool.
 513  0
             try
 514  
             {
 515  0
                 dbCon.close();
 516  
             }
 517  0
             catch (Exception e)
 518  
             {
 519  0
                 log.error("Release of connection failed.", e);
 520  0
             }
 521  0
         }
 522  0
         return exists;
 523  
     }
 524  
 
 525  
     /**
 526  
      * A background thread that tries to ensure that when someone asks
 527  
      * for ids, that there are already some loaded and that the
 528  
      * database is not accessed.
 529  
      */
 530  
     public void run()
 531  
     {
 532  0
         log.debug("IDBroker thread was started.");
 533  
 
 534  0
         Thread thisThread = Thread.currentThread();
 535  0
         while (houseKeeperThread == thisThread)
 536  
         {
 537  
             try
 538  
             {
 539  0
                 Thread.sleep(SLEEP_PERIOD);
 540  
             }
 541  0
             catch (InterruptedException exc)
 542  
             {
 543  
                 // ignored
 544  0
             }
 545  
 
 546  
             // logger.info("IDBroker thread checking for more keys.");
 547  0
             Iterator it = ids.keySet().iterator();
 548  0
             while (it.hasNext())
 549  
             {
 550  0
                 String tableName = (String) it.next();
 551  0
                 if (log.isDebugEnabled())
 552  
                 {
 553  0
                     log.debug("IDBroker thread checking for more keys "
 554  
                             + "on table: " + tableName);
 555  
                 }
 556  0
                 List availableIds = (List) ids.get(tableName);
 557  0
                 int quantity = getQuantity(tableName, null).class="keyword">intValue();
 558  0
                 if (quantity > availableIds.size())
 559  
                 {
 560  
                     try
 561  
                     {
 562  
                         // Second parameter is false because we don't
 563  
                         // want the quantity to be adjusted for thread
 564  
                         // calls.
 565  0
                         storeIDs(tableName, false, null);
 566  0
                         if (log.isDebugEnabled())
 567  
                         {
 568  0
                             log.debug("Retrieved more ids for table: " + tableName);
 569  
                         }
 570  
                     }
 571  0
                     catch (Exception exc)
 572  
                     {
 573  0
                         log.error("There was a problem getting new IDs "
 574  
                                      + "for table: " + tableName, exc);
 575  0
                     }
 576  
                 }
 577  0
             }
 578  0
         }
 579  0
         log.debug("IDBroker thread finished.");
 580  0
     }
 581  
 
 582  
     /**
 583  
      * Shuts down the IDBroker thread.
 584  
      *
 585  
      * Calling this method stops the thread that was started for this
 586  
      * instance of the IDBroker. This method should be called during
 587  
      * MapBroker Service shutdown.
 588  
      */
 589  
     public void stop()
 590  
     {
 591  0
         houseKeeperThread = null;
 592  0
     }
 593  
 
 594  
     /**
 595  
      * Check the frequency of retrieving new ids from the database.
 596  
      * If the frequency is high then we increase the amount (i.e.
 597  
      * quantity column) of ids retrieved on each access.  Tries to
 598  
      * alter number of keys grabbed so that IDBroker retrieves a new
 599  
      * set of ID's prior to their being needed.
 600  
      *
 601  
      * @param tableName The name of the table for which we want an id.
 602  
      */
 603  
     private void checkTiming(String tableName)
 604  
     {
 605  
         // Check if quantity changing is switched on.
 606  
         // If prefetch is turned off, changing quantity does not make sense
 607  0
         if (!configuration.getBoolean(DB_IDBROKER_CLEVERQUANTITY, true)
 608  
             || !configuration.getBoolean(DB_IDBROKER_PREFETCH, true))
 609  
         {
 610  0
             return;
 611  
         }
 612  
 
 613  
         // Get the last id request for this table.
 614  0
         java.util.Date lastTime = (java.util.Date) lastQueryTime.get(tableName);
 615  0
         java.util.Date now = new java.util.Date();
 616  
 
 617  0
         if (lastTime != null)
 618  
         {
 619  0
             long thenLong = lastTime.getTime();
 620  0
             long nowLong = now.getTime();
 621  0
             int timeLapse = (class="keyword">int) (nowLong - thenLong);
 622  0
             if (timeLapse < SLEEP_PERIOD && timeLapse > 0)
 623  
             {
 624  0
                 if (log.isDebugEnabled())
 625  
                 {
 626  0
                     log.debug("Unscheduled retrieval of more ids for table: "
 627  
                             + tableName);
 628  
                 }
 629  
                 // Increase quantity, so that hopefully this does not
 630  
                 // happen again.
 631  0
                 float rate = getQuantity(tableName, null).class="keyword">floatValue()
 632  
                     / (float) timeLapse;
 633  0
                 quantityStore.put(tableName, new BigDecimal(
 634  
                     Math.ceil(SLEEP_PERIOD * rate * SAFETY_MARGIN)));
 635  
             }
 636  
         }
 637  0
         lastQueryTime.put(tableName, now);
 638  0
     }
 639  
 
 640  
     /**
 641  
      * Grabs more ids from the id_table and stores it in the ids
 642  
      * Hashtable.  If adjustQuantity is set to true the amount of id's
 643  
      * retrieved for each call to storeIDs will be adjusted.
 644  
      *
 645  
      * @param tableName The name of the table for which we want an id.
 646  
      * @param adjustQuantity True if amount should be adjusted.
 647  
      * @param connection a Connection
 648  
      * @exception Exception a generic exception.
 649  
      */
 650  
     private synchronized void storeIDs(String tableName,
 651  
                           boolean adjustQuantity,
 652  
                           Connection connection)
 653  
         throws Exception
 654  
     {
 655  0
         BigDecimal nextId = null;
 656  0
         BigDecimal quantity = null;
 657  
 
 658  
         // Block on the table.  Multiple tables are allowed to ask for
 659  
         // ids simultaneously.
 660  
         //        TableMap tMap = dbMap.getTable(tableName);
 661  
         //        synchronized(tMap)  see comment in the getNextIds method
 662  
         //        {
 663  0
         if (adjustQuantity)
 664  
         {
 665  0
             checkTiming(tableName);
 666  
         }
 667  
 
 668  0
         boolean useNewConnection = (connection == null) || (configuration
 669  
                 .getBoolean(DB_IDBROKER_USENEWCONNECTION, true));
 670  
         try
 671  
         {
 672  0
             if (useNewConnection)
 673  
             {
 674  0
                 connection = Transaction.beginOptional(databaseName,
 675  
                     transactionsSupported);
 676  
             }
 677  
 
 678  
             // Write the current value of quantity of keys to grab
 679  
             // to the database, primarily to obtain a write lock
 680  
             // on the table/row, but this value will also be used
 681  
             // as the starting value when an IDBroker is
 682  
             // instantiated.
 683  0
             quantity = getQuantity(tableName, connection);
 684  0
             updateQuantity(connection, tableName, quantity);
 685  
 
 686  
             // Read the next starting ID from the ID_TABLE.
 687  0
             BigDecimal[] results = selectRow(connection, tableName);
 688  0
             nextId = results[0]; // NEXT_ID column
 689  
 
 690  
             // Update the row based on the quantity in the
 691  
             // ID_TABLE.
 692  0
             BigDecimal newNextId = nextId.add(quantity);
 693  0
             updateNextId(connection, tableName, newNextId.toString());
 694  
 
 695  0
             if (useNewConnection)
 696  
             {
 697  0
                 Transaction.commit(connection);
 698  
             }
 699  
         }
 700  0
         catch (Exception e)
 701  
         {
 702  0
             if (useNewConnection)
 703  
             {
 704  0
                 Transaction.rollback(connection);
 705  
             }
 706  0
             throw e;
 707  0
         }
 708  
 
 709  0
         List availableIds = (List) ids.get(tableName);
 710  0
         if (availableIds == null)
 711  
         {
 712  0
             availableIds = new ArrayList();
 713  0
             ids.put(tableName, availableIds);
 714  
         }
 715  
 
 716  
         // Create the ids and store them in the list of available ids.
 717  0
         int numId = quantity.class="keyword">intValue();
 718  0
         for (int i = 0; i < numId; i++)
 719  
         {
 720  0
             availableIds.add(nextId);
 721  0
             nextId = nextId.add(ONE);
 722  
         }
 723  
         //        }
 724  0
     }
 725  
 
 726  
     /**
 727  
      * This method allows you to get the number of ids that are to be
 728  
      * cached in memory.  This is either stored in quantityStore or
 729  
      * read from the db. (ie the value in ID_TABLE.QUANTITY).
 730  
      *
 731  
      * Though this method returns a BigDecimal for the quantity, it is
 732  
      * unlikey the system could withstand whatever conditions would lead
 733  
      * to really needing a large quantity, it is retrieved as a BigDecimal
 734  
      * only because it is going to be added to another BigDecimal.
 735  
      *
 736  
      * @param tableName The name of the table we want to query.
 737  
      * @param connection a Connection
 738  
      * @return An int with the number of ids cached in memory.
 739  
      */
 740  
     private BigDecimal getQuantity(String tableName, Connection connection)
 741  
     {
 742  0
         BigDecimal quantity = null;
 743  
 
 744  
         // If prefetch is turned off we simply return 1
 745  0
         if (!configuration.getBoolean(DB_IDBROKER_PREFETCH, true))
 746  
         {
 747  0
             quantity = new BigDecimal(1);
 748  0
         }
 749  
         // Initialize quantity, if necessary.
 750  0
         else if (quantityStore.containsKey(tableName))
 751  
         {
 752  0
             quantity = (BigDecimal) quantityStore.get(tableName);
 753  0
         }
 754  
         else
 755  
         {
 756  0
             Connection dbCon = null;
 757  
             try
 758  
             {
 759  0
                 if (connection == null || configuration
 760  
                     .getBoolean(DB_IDBROKER_USENEWCONNECTION, true))
 761  
                 {
 762  
                     // Get a connection to the db
 763  0
                     dbCon = Torque.getConnection(databaseName);
 764  
                 }
 765  
 
 766  
                 // Read the row from the ID_TABLE.
 767  0
                 BigDecimal[] results = selectRow(dbCon, tableName);
 768  
 
 769  
                 // QUANTITY column.
 770  0
                 quantity = results[1];
 771  0
                 quantityStore.put(tableName, quantity);
 772  
             }
 773  0
             catch (Exception e)
 774  
             {
 775  0
                 quantity = new BigDecimal(10);
 776  
             }
 777  
             finally
 778  
             {
 779  
                 // Return the connection to the pool.
 780  0
                 try
 781  
                 {
 782  0
                     dbCon.close();
 783  
                 }
 784  0
                 catch (Exception e)
 785  
                 {
 786  0
                     log.error("Release of connection failed.", e);
 787  0
                 }
 788  0
             }
 789  
         }
 790  0
         return quantity;
 791  
     }
 792  
 
 793  
     /**
 794  
      * Helper method to select a row in the ID_TABLE.
 795  
      *
 796  
      * @param con A Connection.
 797  
      * @param tableName The properly escaped name of the table to
 798  
      * identify the row.
 799  
      * @return A BigDecimal[].
 800  
      * @exception Exception a generic exception.
 801  
      */
 802  
     private BigDecimal[] selectRow(Connection con, String tableName)
 803  
         throws Exception
 804  
     {
 805  0
         StringBuffer stmt = new StringBuffer();
 806  0
         stmt.append("SELECT ")
 807  
             .append(COL_NEXT_ID)
 808  
             .append(", ")
 809  
             .append(COL_QUANTITY)
 810  
             .append(" FROM ")
 811  
             .append(ID_TABLE)
 812  
             .append(" WHERE ")
 813  
             .append(COL_TABLE_NAME)
 814  
             .append(" = '")
 815  
             .append(tableName)
 816  
             .append('\'');
 817  
 
 818  0
         Statement statement = null;
 819  
 
 820  0
         BigDecimal[] results = new BigDecimal[2];
 821  
         try
 822  
         {
 823  0
             statement = con.createStatement();
 824  0
             ResultSet rs = statement.executeQuery(stmt.toString());
 825  
 
 826  0
             if (rs.next())
 827  
             {
 828  
                 // work around for MySQL which appears to support
 829  
                 // getBigDecimal in the source code, but the binary
 830  
                 // is throwing an NotImplemented exception.
 831  0
                 results[0] = new BigDecimal(rs.getString(1)); // next_id
 832  0
                 results[1] = new BigDecimal(rs.getString(2)); // quantity
 833  0
             }
 834  
             else
 835  
             {
 836  0
                 throw new TorqueException("The table " + tableName
 837  
                         + " does not have a proper entry in the " + ID_TABLE);
 838  
             }
 839  
         }
 840  
         finally
 841  
         {
 842  0
             if (statement != null)
 843  
             {
 844  0
                 statement.close();
 845  0
             }
 846  0
         }
 847  
 
 848  0
         return results;
 849  
     }
 850  
 
 851  
     /**
 852  
      * Helper method to update a row in the ID_TABLE.
 853  
      *
 854  
      * @param con A Connection.
 855  
      * @param tableName The properly escaped name of the table to identify the
 856  
      * row.
 857  
      * @param id An int with the value to set for the id.
 858  
      * @exception Exception Database error.
 859  
      */
 860  
     private void updateNextId(Connection con, String tableName, String id)
 861  
         throws Exception
 862  
     {
 863  
 
 864  
 
 865  0
         StringBuffer stmt = new StringBuffer(id.length()
 866  
                                              + tableName.length() + 50);
 867  0
         stmt.append("UPDATE " + ID_TABLE)
 868  
             .append(" SET ")
 869  
             .append(COL_NEXT_ID)
 870  
             .append(" = ")
 871  
             .append(id)
 872  
             .append(" WHERE ")
 873  
             .append(COL_TABLE_NAME)
 874  
             .append(" = '")
 875  
             .append(tableName)
 876  
             .append('\'');
 877  
 
 878  0
         Statement statement = null;
 879  
 
 880  0
         if (log.isDebugEnabled())
 881  
         {
 882  0
             log.debug("updateNextId: " + stmt.toString());
 883  
         }
 884  
 
 885  
         try
 886  
         {
 887  0
             statement = con.createStatement();
 888  0
             statement.executeUpdate(stmt.toString());
 889  
         }
 890  
         finally
 891  
         {
 892  0
             if (statement != null)
 893  
             {
 894  0
                 statement.close();
 895  0
             }
 896  0
         }
 897  0
     }
 898  
 
 899  
     /**
 900  
      * Helper method to update a row in the ID_TABLE.
 901  
      *
 902  
      * @param con A Connection.
 903  
      * @param tableName The properly escaped name of the table to identify the
 904  
      * row.
 905  
      * @param quantity An int with the value of the quantity.
 906  
      * @exception Exception Database error.
 907  
      */
 908  
     private void updateQuantity(Connection con, String tableName,
 909  
                                 BigDecimal quantity)
 910  
         throws Exception
 911  
     {
 912  0
         StringBuffer stmt = new StringBuffer(quantity.toString().length()
 913  
                                              + tableName.length() + 50);
 914  0
         stmt.append("UPDATE ")
 915  
             .append(ID_TABLE)
 916  
             .append(" SET ")
 917  
             .append(COL_QUANTITY)
 918  
             .append(" = ")
 919  
             .append(quantity)
 920  
             .append(" WHERE ")
 921  
             .append(COL_TABLE_NAME)
 922  
             .append(" = '")
 923  
             .append(tableName)
 924  
             .append('\'');
 925  
 
 926  0
         Statement statement = null;
 927  
 
 928  0
         if (log.isDebugEnabled())
 929  
         {
 930  0
             log.debug("updateQuantity: " + stmt.toString());
 931  
         }
 932  
 
 933  
         try
 934  
         {
 935  0
             statement = con.createStatement();
 936  0
             statement.executeUpdate(stmt.toString());
 937  
         }
 938  
         finally
 939  
         {
 940  0
             if (statement != null)
 941  
             {
 942  0
                 statement.close();
 943  0
             }
 944  0
         }
 945  0
     }
 946  
 }

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