Coverage report

  %line %branch
org.apache.torque.oid.IDBroker
11% 
64% 

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

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