KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ofbiz > entity > util > SequenceUtil


1 /*
2  * $Id: SequenceUtil.java 5462 2005-08-05 18:35:48Z jonesde $
3  *
4  * Copyright (c) 2001-2005 The Open For Business Project - www.ofbiz.org
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included
14  * in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
21  * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  */

24 package org.ofbiz.entity.util;
25
26 import java.sql.Connection JavaDoc;
27 import java.sql.ResultSet JavaDoc;
28 import java.sql.SQLException JavaDoc;
29 import java.sql.Statement JavaDoc;
30 import java.util.Hashtable JavaDoc;
31 import java.util.Map JavaDoc;
32
33 import javax.transaction.Transaction JavaDoc;
34
35 import org.ofbiz.base.util.Debug;
36 import org.ofbiz.entity.GenericEntityException;
37 import org.ofbiz.entity.jdbc.ConnectionFactory;
38 import org.ofbiz.entity.model.ModelEntity;
39 import org.ofbiz.entity.model.ModelField;
40 import org.ofbiz.entity.transaction.GenericTransactionException;
41 import org.ofbiz.entity.transaction.TransactionUtil;
42
43 /**
44  * Sequence Utility to get unique sequences from named sequence banks
45  * Uses a collision detection approach to safely get unique sequenced ids in banks from the database
46  *
47  * @author <a HREF="mailto:jonesde@ofbiz.org">David E. Jones</a>
48  * @version $Rev: 5462 $
49  * @since 2.0
50  */

51 public class SequenceUtil {
52
53     public static final String JavaDoc module = SequenceUtil.class.getName();
54
55     Map JavaDoc sequences = new Hashtable JavaDoc();
56     String JavaDoc helperName;
57     ModelEntity seqEntity;
58     String JavaDoc tableName;
59     String JavaDoc nameColName;
60     String JavaDoc idColName;
61
62     private SequenceUtil() {}
63
64     public SequenceUtil(String JavaDoc helperName, ModelEntity seqEntity, String JavaDoc nameFieldName, String JavaDoc idFieldName) {
65         this.helperName = helperName;
66         this.seqEntity = seqEntity;
67         if (seqEntity == null) {
68             throw new IllegalArgumentException JavaDoc("The sequence model entity was null but is required.");
69         }
70         this.tableName = seqEntity.getTableName(helperName);
71
72         ModelField nameField = seqEntity.getField(nameFieldName);
73
74         if (nameField == null) {
75             throw new IllegalArgumentException JavaDoc("Could not find the field definition for the sequence name field " + nameFieldName);
76         }
77         this.nameColName = nameField.getColName();
78
79         ModelField idField = seqEntity.getField(idFieldName);
80
81         if (idField == null) {
82             throw new IllegalArgumentException JavaDoc("Could not find the field definition for the sequence id field " + idFieldName);
83         }
84         this.idColName = idField.getColName();
85     }
86
87     public Long JavaDoc getNextSeqId(String JavaDoc seqName, long staggerMax) {
88         SequenceBank bank = (SequenceBank) sequences.get(seqName);
89
90         if (bank == null) {
91             synchronized(this) {
92                 bank = (SequenceBank) sequences.get(seqName);
93                 if (bank == null) {
94                     bank = new SequenceBank(seqName, this);
95                     sequences.put(seqName, bank);
96                 }
97             }
98         }
99         return bank.getNextSeqId(staggerMax);
100     }
101
102     class SequenceBank {
103
104         public static final long defaultBankSize = 10;
105         public static final long startSeqId = 10000;
106         public static final int minWaitMillis = 5;
107         public static final int maxWaitMillis = 50;
108         public static final int maxTries = 5;
109
110         long curSeqId;
111         long maxSeqId;
112         String JavaDoc seqName;
113         SequenceUtil parentUtil;
114
115         public SequenceBank(String JavaDoc seqName, SequenceUtil parentUtil) {
116             this.seqName = seqName;
117             this.parentUtil = parentUtil;
118             curSeqId = 0;
119             maxSeqId = 0;
120             fillBank(1);
121         }
122
123         public synchronized Long JavaDoc getNextSeqId(long staggerMax) {
124             long stagger = 1;
125             if (staggerMax > 1) {
126                 stagger = Math.round(Math.random() * staggerMax);
127                 if (stagger == 0) stagger = 1;
128             }
129             
130             if ((curSeqId + stagger) <= maxSeqId) {
131                 Long JavaDoc retSeqId = new Long JavaDoc(curSeqId);
132                 curSeqId += stagger;
133                 return retSeqId;
134             } else {
135                 fillBank(stagger);
136                 if ((curSeqId + stagger) <= maxSeqId) {
137                     Long JavaDoc retSeqId = new Long JavaDoc(curSeqId);
138                     curSeqId += stagger;
139                     return retSeqId;
140                 } else {
141                     Debug.logError("[SequenceUtil.SequenceBank.getNextSeqId] Fill bank failed, returning null", module);
142                     return null;
143                 }
144             }
145         }
146
147         protected synchronized void fillBank(long stagger) {
148             //Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Starting fillBank Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);
149

150             long bankSize = defaultBankSize;
151             if (stagger > 1) {
152                 // 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
153
bankSize = stagger * defaultBankSize;
154             }
155             
156             // no need to get a new bank, SeqIds available
157
if ((curSeqId + stagger) <= maxSeqId) return;
158                 
159             long val1 = 0;
160             long val2 = 0;
161
162             // NOTE: the fancy ethernet type stuff is for the case where transactions not available
163
Transaction JavaDoc suspendedTransaction = null;
164             try {
165                 //if we can suspend the transaction, we'll try to do this in a local manual transaction
166
suspendedTransaction = TransactionUtil.suspend();
167                 
168                 boolean beganTransaction = false;
169                 try {
170                     beganTransaction = TransactionUtil.begin();
171
172                     Connection JavaDoc connection = null;
173                     Statement JavaDoc stmt = null;
174                     ResultSet JavaDoc rs = null;
175
176                     try {
177                         connection = ConnectionFactory.getConnection(parentUtil.helperName);
178                     } catch (SQLException JavaDoc sqle) {
179                         Debug.logWarning("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was:" + sqle.toString(), module);
180                         throw sqle;
181                     } catch (GenericEntityException e) {
182                         Debug.logWarning("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was: " + e.toString(), module);
183                         throw e;
184                     }
185                     
186                     if (connection == null) {
187                         throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database, connection was null...");
188                     }
189
190                     String JavaDoc sql = null;
191
192                     try {
193                         // we shouldn't need this, and some TX managers complain about it, so not including it: connection.setAutoCommit(false);
194

195                         stmt = connection.createStatement();
196                         int numTries = 0;
197
198                         while (val1 + bankSize != val2) {
199                             if (Debug.verboseOn()) Debug.logVerbose("[SequenceUtil.SequenceBank.fillBank] Trying to get a bank of sequenced ids for " +
200                                     this.seqName + "; start of loop val1=" + val1 + ", val2=" + val2 + ", bankSize=" + bankSize, module);
201                             
202                             sql = "SELECT " + parentUtil.idColName + " FROM " + parentUtil.tableName + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'";
203                             rs = stmt.executeQuery(sql);
204                             boolean gotVal1 = false;
205                             if (rs.next()) {
206                                 val1 = rs.getLong(parentUtil.idColName);
207                                 gotVal1 = true;
208                             }
209                             rs.close();
210                             
211                             if (!gotVal1) {
212                                 Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] first select failed: will try to add new row, result set was empty for sequence [" + seqName + "] \nUsed SQL: " + sql + " \n Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);
213                                 sql = "INSERT INTO " + parentUtil.tableName + " (" + parentUtil.nameColName + ", " + parentUtil.idColName + ") VALUES ('" + this.seqName + "', " + startSeqId + ")";
214                                 if (stmt.executeUpdate(sql) <= 0) {
215                                     throw new GenericEntityException("No rows changed when trying insert new sequence row with this SQL: " + sql);
216                                 }
217                                 continue;
218                             }
219
220                             sql = "UPDATE " + parentUtil.tableName + " SET " + parentUtil.idColName + "=" + parentUtil.idColName + "+" + bankSize + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'";
221                             if (stmt.executeUpdate(sql) <= 0) {
222                                 throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] update failed, no rows changes for seqName: " + seqName);
223                             }
224
225                             sql = "SELECT " + parentUtil.idColName + " FROM " + parentUtil.tableName + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'";
226                             rs = stmt.executeQuery(sql);
227                             boolean gotVal2 = false;
228                             if (rs.next()) {
229                                 val2 = rs.getLong(parentUtil.idColName);
230                                 gotVal2 = true;
231                             }
232
233                             rs.close();
234                             
235                             if (!gotVal2) {
236                                 throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] second select failed: aborting, result " + "set was empty for sequence: " + seqName);
237                                 
238                             }
239
240                             if (val1 + bankSize != val2) {
241                                 if (numTries >= maxTries) {
242                                     throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] maxTries (" + maxTries + ") reached, giving up.");
243                                 }
244                                 
245                                 // collision happened, wait a bounded random amount of time then continue
246
int waitTime = (new Double JavaDoc(Math.random() * (maxWaitMillis - minWaitMillis))).intValue() + minWaitMillis;
247
248                                 Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Collision found for seqName [" + seqName + "], val1=" + val1 + ", val2=" + val2 + ", val1+bankSize=" + (val1 + bankSize) + ", bankSize=" + bankSize + ", waitTime=" + waitTime, module);
249
250                                 try {
251                                     this.wait(waitTime);
252                                 } catch (Exception JavaDoc e) {
253                                     Debug.logWarning(e, "Error waiting in sequence util", module);
254                                     throw e;
255                                 }
256                             }
257
258                             numTries++;
259                         }
260
261                         curSeqId = val1;
262                         maxSeqId = val2;
263                         if (Debug.infoOn()) Debug.logInfo("Got bank of sequenced IDs for [" + this.seqName + "]; curSeqId=" + curSeqId + ", maxSeqId=" + maxSeqId + ", bankSize=" + bankSize, module);
264                     } catch (SQLException JavaDoc sqle) {
265                         Debug.logWarning(sqle, "[SequenceUtil.SequenceBank.fillBank] SQL Exception while executing the following:\n" + sql + "\nError was:" + sqle.getMessage(), module);
266                         throw sqle;
267                     } finally {
268                         try {
269                             if (stmt != null) stmt.close();
270                         } catch (SQLException JavaDoc sqle) {
271                             Debug.logWarning(sqle, "Error closing statement in sequence util", module);
272                         }
273                         try {
274                             if (connection != null) connection.close();
275                         } catch (SQLException JavaDoc sqle) {
276                             Debug.logWarning(sqle, "Error closing connection in sequence util", module);
277                         }
278                     }
279                 } catch (Exception JavaDoc e) {
280                     String JavaDoc errMsg = "General error in getting a sequenced ID";
281                     Debug.logError(e, errMsg, module);
282                     try {
283                         TransactionUtil.rollback(beganTransaction, errMsg, e);
284                     } catch (GenericTransactionException gte2) {
285                         Debug.logError(gte2, "Unable to rollback transaction", module);
286                     }
287                 } finally {
288                     try {
289                         TransactionUtil.commit(beganTransaction);
290                     } catch (GenericTransactionException gte) {
291                         Debug.logError(gte, "Unable to commit transaction", module);
292                     }
293                 }
294             } catch (GenericTransactionException e) {
295                 Debug.logError(e, "System Error suspending transaction in sequence util", module);
296             } finally {
297                 if (suspendedTransaction != null) {
298                     try {
299                         TransactionUtil.resume(suspendedTransaction);
300                     } catch (GenericTransactionException e) {
301                         Debug.logError(e, "Error resuming suspended transaction in sequence util", module);
302                     }
303                 }
304             }
305             //Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Ending fillBank Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);
306
}
307     }
308 }
309
Popular Tags