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