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