001: /*
002: * $Id: SequenceUtil.java,v 1.3 2004/02/06 22:13:25 jonesde Exp $
003: *
004: * Copyright (c) 2001, 2002 The Open For Business Project - www.ofbiz.org
005: *
006: * Permission is hereby granted, free of charge, to any person obtaining a
007: * copy of this software and associated documentation files (the "Software"),
008: * to deal in the Software without restriction, including without limitation
009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
010: * and/or sell copies of the Software, and to permit persons to whom the
011: * Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included
014: * in all copies or substantial portions of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023: */
024: package org.ofbiz.entity.util;
025:
026: import java.sql.Connection;
027: import java.sql.ResultSet;
028: import java.sql.SQLException;
029: import java.sql.Statement;
030: import java.util.Hashtable;
031: import java.util.Map;
032:
033: import javax.transaction.InvalidTransactionException;
034: import javax.transaction.SystemException;
035: import javax.transaction.Transaction;
036: import javax.transaction.TransactionManager;
037:
038: import org.ofbiz.base.util.Debug;
039: import org.ofbiz.entity.GenericEntityException;
040: import org.ofbiz.entity.jdbc.ConnectionFactory;
041: import org.ofbiz.entity.model.ModelEntity;
042: import org.ofbiz.entity.model.ModelField;
043: import org.ofbiz.entity.transaction.GenericTransactionException;
044: import org.ofbiz.entity.transaction.TransactionFactory;
045: import org.ofbiz.entity.transaction.TransactionUtil;
046:
047: /**
048: * Sequence Utility to get unique sequences from named sequence banks
049: * Uses a collision detection approach to safely get unique sequenced ids in banks from the database
050: *
051: * @author <a href="mailto:jonesde@ofbiz.org">David E. Jones</a>
052: * @version $Revision: 1.3 $
053: * @since 2.0
054: */
055: public class SequenceUtil {
056:
057: public static final String module = SequenceUtil.class.getName();
058:
059: Map sequences = new Hashtable();
060: String helperName;
061: ModelEntity seqEntity;
062: String tableName;
063: String nameColName;
064: String idColName;
065:
066: private SequenceUtil() {
067: }
068:
069: public SequenceUtil(String helperName, ModelEntity seqEntity,
070: String nameFieldName, String idFieldName) {
071: this .helperName = helperName;
072: this .seqEntity = seqEntity;
073: if (seqEntity == null) {
074: throw new IllegalArgumentException(
075: "The sequence model entity was null but is required.");
076: }
077: this .tableName = seqEntity.getTableName(helperName);
078:
079: ModelField nameField = seqEntity.getField(nameFieldName);
080:
081: if (nameField == null) {
082: throw new IllegalArgumentException(
083: "Could not find the field definition for the sequence name field "
084: + nameFieldName);
085: }
086: this .nameColName = nameField.getColName();
087:
088: ModelField idField = seqEntity.getField(idFieldName);
089:
090: if (idField == null) {
091: throw new IllegalArgumentException(
092: "Could not find the field definition for the sequence id field "
093: + idFieldName);
094: }
095: this .idColName = idField.getColName();
096: }
097:
098: public Long getNextSeqId(String seqName, long staggerMax) {
099: SequenceBank bank = (SequenceBank) sequences.get(seqName);
100:
101: if (bank == null) {
102: bank = new SequenceBank(seqName, this );
103: sequences.put(seqName, bank);
104: }
105: return bank.getNextSeqId(staggerMax);
106: }
107:
108: class SequenceBank {
109:
110: public static final long defaultBankSize = 10;
111: public static final long startSeqId = 10000;
112: public static final int minWaitNanos = 500000; // 1/2 ms
113: public static final int maxWaitNanos = 1000000; // 1 ms
114: public static final int maxTries = 5;
115:
116: long curSeqId;
117: long maxSeqId;
118: String seqName;
119: SequenceUtil parentUtil;
120:
121: public SequenceBank(String seqName, SequenceUtil parentUtil) {
122: this .seqName = seqName;
123: this .parentUtil = parentUtil;
124: curSeqId = 0;
125: maxSeqId = 0;
126: fillBank(1);
127: }
128:
129: public synchronized Long getNextSeqId(long staggerMax) {
130: long stagger = 1;
131: if (staggerMax > 1) {
132: stagger = Math.round(Math.random() * staggerMax);
133: if (stagger == 0)
134: stagger = 1;
135: }
136:
137: if ((curSeqId + stagger) <= maxSeqId) {
138: Long retSeqId = new Long(curSeqId);
139: curSeqId += stagger;
140: return retSeqId;
141: } else {
142: fillBank(stagger);
143: if ((curSeqId + stagger) <= maxSeqId) {
144: Long retSeqId = new Long(curSeqId);
145: curSeqId += stagger;
146: return retSeqId;
147: } else {
148: Debug
149: .logError(
150: "[SequenceUtil.SequenceBank.getNextSeqId] Fill bank failed, returning null",
151: module);
152: return null;
153: }
154: }
155: }
156:
157: protected synchronized void fillBank(long stagger) {
158: long bankSize = defaultBankSize;
159: if (stagger > 1) {
160: // NOTE: could use staggerMax for this, but if that is done it would be easier to guess a valid next id without a brute force attack
161: bankSize = stagger * defaultBankSize;
162: }
163:
164: // no need to get a new bank, SeqIds available
165: if ((curSeqId + stagger) <= maxSeqId)
166: return;
167:
168: long val1 = 0;
169: long val2 = 0;
170:
171: // NOTE: the fancy ethernet type stuff is for the case where transactions not available
172: boolean manualTX = true;
173: Transaction suspendedTransaction = null;
174: TransactionManager transactionManager = null;
175:
176: try {
177: if (TransactionUtil.getStatus() == TransactionUtil.STATUS_ACTIVE) {
178: manualTX = false;
179: try {
180: //if we can suspend the transaction, we'll try to do this in a local manual transaction
181: transactionManager = TransactionFactory
182: .getTransactionManager();
183: if (transactionManager != null) {
184: suspendedTransaction = transactionManager
185: .suspend();
186: manualTX = true;
187: }
188: } catch (SystemException e) {
189: Debug
190: .logError(
191: e,
192: "System Error suspending transaction in sequence util",
193: module);
194: }
195: }
196: } catch (GenericTransactionException e) {
197: // nevermind, don't worry about it, but print the exc anyway
198: Debug
199: .logWarning(
200: "[SequenceUtil.SequenceBank.fillBank] Exception was thrown trying to check "
201: + "transaction status: "
202: + e.toString(), module);
203: }
204:
205: Connection connection = null;
206: Statement stmt = null;
207: ResultSet rs = null;
208:
209: try {
210: connection = ConnectionFactory
211: .getConnection(parentUtil.helperName);
212: } catch (SQLException sqle) {
213: Debug
214: .logWarning(
215: "[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was:"
216: + sqle.toString(), module);
217: return;
218: } catch (GenericEntityException e) {
219: Debug
220: .logWarning(
221: "[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was: "
222: + e.toString(), module);
223: return;
224: }
225:
226: if (connection == null) {
227: Debug
228: .logWarning(
229: "[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database, connection was null...",
230: module);
231: return;
232: }
233:
234: String sql = null;
235:
236: try {
237: try {
238: connection.setAutoCommit(false);
239: } catch (SQLException sqle) {
240: manualTX = false;
241: }
242:
243: stmt = connection.createStatement();
244: int numTries = 0;
245:
246: while (val1 + bankSize != val2) {
247: if (Debug.verboseOn())
248: Debug
249: .logVerbose(
250: "[SequenceUtil.SequenceBank.fillBank] Trying to get a bank of sequenced ids for "
251: + this .seqName
252: + "; start of loop val1="
253: + val1
254: + ", val2="
255: + val2
256: + ", bankSize="
257: + bankSize, module);
258: sql = "SELECT " + parentUtil.idColName + " FROM "
259: + parentUtil.tableName + " WHERE "
260: + parentUtil.nameColName + "='"
261: + this .seqName + "'";
262: rs = stmt.executeQuery(sql);
263: if (rs.next()) {
264: val1 = rs.getInt(parentUtil.idColName);
265: } else {
266: Debug
267: .logWarning(
268: "[SequenceUtil.SequenceBank.fillBank] first select failed: trying to add "
269: + "row, result set was empty for sequence: "
270: + seqName, module);
271: try {
272: if (rs != null)
273: rs.close();
274: } catch (SQLException sqle) {
275: Debug
276: .logWarning(
277: sqle,
278: "Error closing result set in sequence util",
279: module);
280: }
281: sql = "INSERT INTO " + parentUtil.tableName
282: + " (" + parentUtil.nameColName + ", "
283: + parentUtil.idColName + ") VALUES ('"
284: + this .seqName + "', " + startSeqId
285: + ")";
286: if (stmt.executeUpdate(sql) <= 0)
287: return;
288: continue;
289: }
290: try {
291: if (rs != null)
292: rs.close();
293: } catch (SQLException sqle) {
294: Debug
295: .logWarning(
296: sqle,
297: "Error closing result set in sequence util",
298: module);
299: }
300:
301: sql = "UPDATE " + parentUtil.tableName + " SET "
302: + parentUtil.idColName + "="
303: + parentUtil.idColName + "+" + bankSize
304: + " WHERE " + parentUtil.nameColName + "='"
305: + this .seqName + "'";
306: if (stmt.executeUpdate(sql) <= 0) {
307: Debug
308: .logWarning(
309: "[SequenceUtil.SequenceBank.fillBank] update failed, no rows changes for seqName: "
310: + seqName, module);
311: return;
312: }
313:
314: if (manualTX) {
315: connection.commit();
316: }
317:
318: sql = "SELECT " + parentUtil.idColName + " FROM "
319: + parentUtil.tableName + " WHERE "
320: + parentUtil.nameColName + "='"
321: + this .seqName + "'";
322: rs = stmt.executeQuery(sql);
323: if (rs.next()) {
324: val2 = rs.getInt(parentUtil.idColName);
325: } else {
326: Debug
327: .logWarning(
328: "[SequenceUtil.SequenceBank.fillBank] second select failed: aborting, result "
329: + "set was empty for sequence: "
330: + seqName, module);
331: try {
332: if (rs != null)
333: rs.close();
334: } catch (SQLException sqle) {
335: Debug
336: .logWarning(
337: sqle,
338: "Error closing result set in sequence util",
339: module);
340: }
341: return;
342: }
343: try {
344: if (rs != null)
345: rs.close();
346: } catch (SQLException sqle) {
347: Debug
348: .logWarning(
349: sqle,
350: "Error closing result set in sequence util",
351: module);
352: }
353:
354: if (val1 + bankSize != val2) {
355: if (numTries >= maxTries) {
356: Debug.logError(
357: "[SequenceUtil.SequenceBank.fillBank] maxTries ("
358: + maxTries
359: + ") reached, giving up.",
360: module);
361: return;
362: }
363: // collision happened, wait a bounded random amount of time then continue
364: int waitTime = (new Double(Math.random()
365: * (maxWaitNanos - minWaitNanos)))
366: .intValue()
367: + minWaitNanos;
368:
369: try {
370: this .wait(0, waitTime);
371: } catch (Exception e) {
372: Debug.logWarning(e,
373: "Error waiting in sequence util",
374: module);
375: }
376: }
377:
378: numTries++;
379: }
380:
381: curSeqId = val1;
382: maxSeqId = val2;
383: if (Debug.verboseOn())
384: Debug
385: .logVerbose(
386: "[SequenceUtil.SequenceBank.fillBank] Successfully got a bank of sequenced ids for "
387: + this .seqName
388: + "; curSeqId="
389: + curSeqId
390: + ", maxSeqId="
391: + maxSeqId
392: + ", bankSize=" + bankSize,
393: module);
394: } catch (SQLException sqle) {
395: Debug
396: .logWarning(
397: "[SequenceUtil.SequenceBank.fillBank] SQL Exception while executing the following:\n"
398: + sql + "\nError was:", module);
399: Debug.logWarning(sqle.getMessage(), module);
400: return;
401: } finally {
402: try {
403: if (stmt != null)
404: stmt.close();
405: } catch (SQLException sqle) {
406: Debug.logWarning(sqle,
407: "Error closing statement in sequence util",
408: module);
409: }
410: try {
411: if (connection != null)
412: connection.close();
413: } catch (SQLException sqle) {
414: Debug
415: .logWarning(
416: sqle,
417: "Error closing connection in sequence util",
418: module);
419: }
420: }
421:
422: if (suspendedTransaction != null) {
423: try {
424: if (transactionManager == null) {
425: transactionManager = TransactionFactory
426: .getTransactionManager();
427: }
428: if (transactionManager != null) {
429: transactionManager.resume(suspendedTransaction);
430: }
431: } catch (InvalidTransactionException e) {
432: Debug
433: .logError(
434: e,
435: "InvalidTransaction Error resuming suspended transaction in sequence util",
436: module);
437: } catch (IllegalStateException e) {
438: Debug
439: .logError(
440: e,
441: "IllegalState Error resuming suspended transaction in sequence util",
442: module);
443: } catch (SystemException e) {
444: Debug
445: .logError(
446: e,
447: "System Error resuming suspended transaction in sequence util",
448: module);
449: }
450: }
451: }
452: }
453: }
|