001: /*
002: CREATE TABLE SEQUENCES(
003: SEQUENCE_ID VARCHAR(30) NOT NULL,
004: ID INTEGER NOT NULL,
005: CONSTRAINT PK_SEQUENCE PRIMARY KEY(SEQUENCE_ID)
006: );
007:
008: INSERT INTO SEQUENCES VALUES('MY_SEQUENCE', 0); // FOR EACH TABLE...
009:
010: -HIGH/LOW ID pattern implementation-
011: */
012:
013: package org.julp;
014:
015: import java.lang.reflect.Constructor;
016: import java.lang.reflect.InvocationTargetException;
017: import java.sql.SQLException;
018: import java.util.ArrayList;
019: import java.util.Collection;
020: import java.util.HashMap;
021: import java.util.Map;
022: import java.math.BigInteger;
023: import java.math.BigDecimal;
024: import java.util.Collections;
025: import java.util.Set;
026: import java.util.HashSet;
027:
028: public class KeyGenerator {
029:
030: protected String table; // required
031: protected String idColumn; // required
032: protected String sequenceNameColumn; // required for non-externalSequence
033: protected int increment = 10; // any number greater than zero
034: /** DO NOT USE THE SAME CONNECTION AS THE REST OF THE APPLICATION SINCE CONNECTION FOR KeyGenerator COMMITS ALWAYS!!! */
035: protected DBServices dbServices;
036: protected int retryMax = 3; // any number greater than zero
037: protected long timeout = 250; // any number greater than zero
038: protected String sqlState = "23000"; // SQLSTATE - duplicate key
039: protected int vendorErrorCode = Integer.MIN_VALUE;
040: protected int isolationLevel = -1;
041: protected boolean isolationLevelSet = false;
042: protected Map lowValues = Collections
043: .synchronizedMap(new HashMap());
044: protected Map hiValues = Collections.synchronizedMap(new HashMap());
045: protected Class keyClass = Integer.class; // any class which extends java.lang.Number
046: protected boolean externalSequence = false; // used with "real" sequences: Oracle, PostgreSQL, etc...
047: protected String select;
048: protected String update;
049: protected String insert; // if not exists automatically creates new sequence upon request
050: protected boolean createSequenceOnDemand = true;
051: protected Number initialValue = new Integer(0); // not used with "real" sequences
052: protected Set sequences;
053: protected boolean cacheStatements;
054:
055: public KeyGenerator() {
056: }
057:
058: public KeyGenerator(int isolationLevel) {
059: this .isolationLevel = isolationLevel;
060: }
061:
062: public KeyGenerator(String table, String sequenceNameColumn,
063: String idColumn, int isolationLevel) {
064: this .table = table;
065: this .sequenceNameColumn = sequenceNameColumn;
066: this .idColumn = idColumn;
067: this .isolationLevel = isolationLevel;
068: }
069:
070: public KeyGenerator(String table, String sequenceNameColumn,
071: String idColumn) {
072: this .table = table;
073: this .sequenceNameColumn = sequenceNameColumn;
074: this .idColumn = idColumn;
075: }
076:
077: public KeyGenerator(String table, String sequenceNameColumn,
078: String idColumn, int isolationLevel,
079: boolean externalSequence) {
080: this .table = table;
081: this .sequenceNameColumn = sequenceNameColumn;
082: this .idColumn = idColumn;
083: this .isolationLevel = isolationLevel;
084: this .externalSequence = externalSequence;
085: }
086:
087: public KeyGenerator(String table, String sequenceNameColumn,
088: String idColumn, boolean externalSequence) {
089: this .table = table;
090: this .sequenceNameColumn = sequenceNameColumn;
091: this .idColumn = idColumn;
092: this .externalSequence = externalSequence;
093: }
094:
095: public void init() {
096: if (!externalSequence && this .table == null) {
097: throw new IllegalStateException("Table name is missing");
098: }
099: if (!externalSequence && this .sequenceNameColumn == null) {
100: throw new IllegalStateException(
101: "Sequence column name is missing");
102: }
103: if (this .idColumn == null) {
104: throw new IllegalStateException("ID column name is missing");
105: }
106: if (dbServices == null) {
107: throw new IllegalStateException("DBServices not set");
108: }
109: if (!externalSequence) {
110: // SELECT ID FROM SEQUENCES WHERE SEQUENCE_NAME = ? ${sequence}
111: select = "SELECT " + idColumn + " FROM " + table
112: + " WHERE " + sequenceNameColumn + " = ?";
113: // UPDATE SEQUENCES SET ID = ID + 10 WHERE SEQUENCE_NAME = ? AND ID = ? ${current ID}
114: update = "UPDATE " + table + " SET " + idColumn + " = "
115: + idColumn + " + " + String.valueOf(increment)
116: + " WHERE " + sequenceNameColumn + " = ? AND "
117: + idColumn + " = ?";
118: if (createSequenceOnDemand) {
119: // INSERT INTO SEQUENCES (SEQUENCE_NAME, ID) VALUES (?, 0) ${sequence}
120: insert = "INSERT INTO " + table + " ("
121: + sequenceNameColumn + ", " + idColumn
122: + ") VALUES (?, " + initialValue.toString()
123: + ")";
124: }
125: String allSequences = "SELECT " + sequenceNameColumn
126: + " FROM " + table;
127: try {
128: sequences = Collections
129: .synchronizedSet(new HashSet(
130: dbServices
131: .getSingleColumnResultAsList(allSequences)));
132: } catch (SQLException sqle) {
133: throw new RuntimeException(sqle);
134: }
135: }
136: }
137:
138: public synchronized Number getNextValue(String sequence)
139: throws SQLException {
140: if (!externalSequence && this .select == null) {
141: throw new IllegalStateException(
142: "KeyGenerator not initialized");
143: }
144: if (!externalSequence && !sequences.contains(sequence)
145: && createSequenceOnDemand) { // new sequence
146: Collection param = new ArrayList(1);
147: param.add(sequence);
148: dbServices.execute(insert, param);
149: sequences.add(sequence);
150: }
151: Object value = lowValues.get(sequenceNameColumn);
152: Number hiValue = null;
153: if (value == null) {
154: value = getSeed(sequence);
155: lowValues.put(sequenceNameColumn, value);
156: if (keyClass.getName().equals("java.lang.Integer")) {
157: hiValue = ((Number) value).intValue() + increment;
158: } else if (keyClass.getName().equals("java.lang.Long")) {
159: hiValue = ((Number) value).longValue() + increment;
160: } else if (keyClass.getName()
161: .equals("java.math.BigInteger")) {
162: hiValue = new BigInteger(value.toString())
163: .add(new BigInteger(String.valueOf(increment)));
164: } else if (keyClass.getName()
165: .equals("java.math.BigDecimal")) {
166: hiValue = new BigDecimal(value.toString())
167: .add(new BigDecimal(String.valueOf(increment)));
168: } else {
169: try {
170: hiValue = new BigDecimal(value.toString())
171: .add(new BigDecimal(String
172: .valueOf(increment)));
173: Constructor c = keyClass
174: .getConstructor(String.class);
175: hiValue = (Number) c
176: .newInstance(hiValue.toString());
177: } catch (InvocationTargetException ite) {
178: throw new SQLException(ite.getTargetException()
179: .toString());
180: } catch (Exception e) {
181: throw new SQLException(e.toString());
182: }
183: }
184: hiValues.put(sequenceNameColumn, hiValue);
185: } else {
186: if (keyClass.getName().equals("java.lang.Integer")) {
187: if (((Number) value).intValue() < ((Number) hiValues
188: .get(sequenceNameColumn)).intValue() - 1) {
189: value = new Integer(((Number) value).intValue() + 1);
190: lowValues.put(sequenceNameColumn, value);
191: } else {
192: value = getSeed(sequence);
193: lowValues.put(sequenceNameColumn, value);
194: hiValues.put(sequenceNameColumn, ((Number) value)
195: .intValue()
196: + increment);
197: }
198: } else if (keyClass.getName().equals("java.lang.Long")) {
199: if (((Number) value).longValue() < ((Number) hiValues
200: .get(sequenceNameColumn)).longValue() - 1) {
201: value = new Long(((Number) value).longValue() + 1);
202: lowValues.put(sequenceNameColumn, value);
203: } else {
204: value = getSeed(sequence);
205: lowValues.put(sequenceNameColumn, value);
206: hiValues.put(sequenceNameColumn, ((Number) value)
207: .longValue()
208: + increment);
209: }
210: } else if (keyClass.getName()
211: .equals("java.math.BigInteger")) {
212: if (new BigInteger(value.toString())
213: .compareTo(new BigInteger(hiValues.get(
214: sequenceNameColumn).toString())) == -1) {
215: value = new BigInteger(value.toString())
216: .add(BigInteger.ONE);
217: lowValues.put(sequenceNameColumn, value);
218: } else {
219: value = getSeed(sequence);
220: lowValues.put(sequenceNameColumn, value);
221: hiValues.put(sequenceNameColumn, new BigInteger(
222: value.toString()).add(new BigInteger(String
223: .valueOf(increment))));
224: }
225: } else if (keyClass.getName()
226: .equals("java.math.BigDecimal")) {
227: if (new BigDecimal(value.toString())
228: .compareTo(new BigDecimal(hiValues.get(
229: sequenceNameColumn).toString())) == -1) {
230: value = new BigDecimal(value.toString())
231: .add(BigDecimal.ONE);
232: lowValues.put(sequenceNameColumn, value);
233: } else {
234: value = getSeed(sequence);
235: lowValues.put(sequenceNameColumn, value);
236: hiValues.put(sequenceNameColumn, new BigDecimal(
237: value.toString()).add(new BigDecimal(String
238: .valueOf(increment))));
239: }
240: } else {
241: if (new BigDecimal(value.toString())
242: .compareTo(new BigDecimal(hiValues.get(
243: sequenceNameColumn).toString())) == -1) {
244: value = new BigDecimal(value.toString())
245: .add(BigDecimal.ONE);
246: try {
247: value = new BigDecimal(value.toString())
248: .add(BigDecimal.ONE);
249: Constructor c = keyClass
250: .getConstructor(String.class);
251: lowValues.put(sequenceNameColumn, c
252: .newInstance(value.toString()));
253: } catch (InvocationTargetException ite) {
254: throw new SQLException(ite.getTargetException()
255: .toString());
256: } catch (Exception e) {
257: throw new SQLException(e.toString());
258: }
259: } else {
260: value = getSeed(sequence);
261: lowValues.put(sequenceNameColumn, value);
262: try {
263: Constructor c = keyClass
264: .getConstructor(String.class);
265: BigDecimal temp = new BigDecimal(value
266: .toString()).add(new BigDecimal(String
267: .valueOf(increment)));
268: hiValues.put(sequenceNameColumn, c
269: .newInstance(temp.toString()));
270: } catch (InvocationTargetException ite) {
271: throw new SQLException(ite.getTargetException()
272: .toString());
273: } catch (Exception e) {
274: throw new SQLException(e.toString());
275: }
276: }
277: }
278: }
279: return (Number) value;
280: }
281:
282: public synchronized Number getCurrentValue(String sequence)
283: throws SQLException {
284: if (!externalSequence && this .select == null) {
285: throw new IllegalStateException(
286: "KeyGenerator not initialized");
287: }
288: if (!externalSequence && !sequences.contains(sequence)
289: && createSequenceOnDemand) { // new sequence
290: Collection param = new ArrayList(1);
291: param.add(sequence);
292: dbServices.execute(insert, param);
293: sequences.add(sequence);
294: }
295: Object currentValue;
296: try {
297: if (externalSequence) {
298: if (table == null || table.trim().length() == 0) {
299: currentValue = dbServices.getSingleValue("SELECT "
300: + idColumn);
301: } else {
302: currentValue = dbServices.getSingleValue("SELECT "
303: + idColumn + " FROM " + table);
304: }
305: } else {
306: Collection param = new ArrayList(1);
307: param.add(sequence);
308: currentValue = dbServices.getSingleValue(select, param);
309: }
310: } catch (SQLException sqle) {
311: throw sqle;
312: } finally {
313: dbServices.release(false);
314: }
315: return (Number) currentValue;
316: }
317:
318: protected synchronized Number getSeed(String sequence)
319: throws SQLException {
320: if (!externalSequence && this .select == null) {
321: throw new IllegalStateException(
322: "KeyGenerator not initialized");
323: }
324: Object id = null;
325: boolean success = false;
326: if (externalSequence) {
327: try {
328: id = dbServices.getSingleValue("SELECT " + idColumn
329: + " FROM " + table);
330: success = true;
331: } finally {
332: dbServices.release(false);
333: }
334: } else {
335: try {
336: int retryCount = 0;
337: if (isolationLevel != -1 && !isolationLevelSet) { // set it only once
338: dbServices.getConnection().setTransactionIsolation(
339: isolationLevel);
340: isolationLevelSet = true;
341: }
342: dbServices.beginTran();
343: while (!success && retryCount <= retryMax) {
344: try {
345: Collection param = new ArrayList(1);
346: param.add(sequence);
347: System.out
348: .println("========== param: " + param);
349: id = dbServices.getSingleValue(select, param);
350: param.add(id);
351: dbServices.execute(update, param);
352: success = true;
353: if (keyClass.getName().equals(
354: "java.lang.Integer")) {
355: id = new Integer(((Number) id).intValue()
356: + increment);
357: } else if (keyClass.getName().equals(
358: "java.lang.Long")) {
359: id = new Long(((Number) id).intValue()
360: + increment);
361: } else if (keyClass.getName().equals(
362: "java.math.BigInteger")) {
363: id = new BigInteger(id.toString())
364: .add(new BigInteger(String
365: .valueOf(increment)));
366: } else if (keyClass.getName().equals(
367: "java.math.BigDecimal")) {
368: id = new BigDecimal(id.toString())
369: .add(new BigDecimal(String
370: .valueOf(increment)));
371: } else {
372: Constructor c = keyClass
373: .getConstructor(String.class);
374: id = c.newInstance(String
375: .valueOf(increment));
376: }
377: break;
378: } catch (InstantiationException ie) {
379: throw new SQLException(ie.toString());
380: } catch (IllegalAccessException iae) {
381: throw new SQLException(iae.toString());
382: } catch (InvocationTargetException ite) {
383: throw new SQLException(ite.getTargetException()
384: .toString());
385: } catch (SQLException sqle) {
386: if ((sqle.getSQLState() != null && sqle
387: .getSQLState().equals(sqlState))
388: || sqle.getErrorCode() == vendorErrorCode) {
389: // duplicate key: retry
390: } else {
391: throw sqle; // some other problem
392: }
393: }
394: retryCount++;
395: try {
396: wait(timeout);
397: } catch (InterruptedException ie) {
398: throw new SQLException("Cannot get unique ID");
399: }
400: }
401: } catch (NoSuchMethodException nsme) {
402: throw new SQLException(nsme.getMessage()); // should not happend
403: } catch (SQLException sqle) {
404: throw sqle;
405: } finally {
406: dbServices.commitTran(); // commit no matter what
407: dbServices.release(false);
408: }
409: }
410: if (!success) {
411: throw new SQLException("Cannot get unique ID");
412: }
413: return (Number) id;
414: }
415:
416: public String getTable() {
417: return table;
418: }
419:
420: public void setTable(String table) {
421: this .table = table;
422: }
423:
424: public String getIdColumn() {
425: return idColumn;
426: }
427:
428: public void setIdColumn(String idColumn) {
429: this .idColumn = idColumn;
430: }
431:
432: public int getIncrement() {
433: return increment;
434: }
435:
436: public void setIncrement(int increment) {
437: this .increment = increment;
438: }
439:
440: public DBServices getDBServices() {
441: return dbServices;
442: }
443:
444: public void setDBServices(DBServices dbServices) {
445: this .dbServices = dbServices;
446: this .dbServices.setCacheStatements(cacheStatements);
447: }
448:
449: public int getRetryMax() {
450: return retryMax;
451: }
452:
453: public void setRetryMax(int retryMax) {
454: this .retryMax = retryMax;
455: }
456:
457: public String getSqlState() {
458: return sqlState;
459: }
460:
461: public void setSqlState(String sqlState) {
462: this .sqlState = sqlState;
463: }
464:
465: public long getTimeout() {
466: return timeout;
467: }
468:
469: public void setTimeout(long timeout) {
470: if (timeout < 0) {
471: throw new IllegalArgumentException(
472: "Timeout must be greater than zero");
473: }
474: this .timeout = timeout;
475: }
476:
477: public int getIsolationLevel() {
478: return isolationLevel;
479: }
480:
481: public void setIsolationLevel(int isolationLevel) {
482: this .isolationLevel = isolationLevel;
483: }
484:
485: public String getSequenceNameColumn() {
486: return sequenceNameColumn;
487: }
488:
489: public void setSequenceNameColumn(String sequenceNameColumn) {
490: this .sequenceNameColumn = sequenceNameColumn;
491: }
492:
493: public Class getKeyClass() {
494: return keyClass;
495: }
496:
497: public void setKeyClass(Class keyClass) {
498: if (Number.class.isAssignableFrom(keyClass)) {
499: this .keyClass = keyClass;
500: } else {
501: throw new IllegalArgumentException(
502: "KeyClass must extend java.lang.Number");
503: }
504: try {
505: Constructor c = keyClass.getConstructor(String.class);
506: Object obj = c.newInstance("0");
507: String stringValue = obj.toString();
508: if (stringValue == null
509: && !(stringValue.equals("0") && !(stringValue
510: .startsWith("0.")))) {
511: throw new IllegalArgumentException(
512: keyClass.getName()
513: + ".toString() must return String value of Number");
514: }
515: } catch (InvocationTargetException ite) {
516: throw new IllegalArgumentException(ite.getTargetException()
517: .toString());
518: } catch (Exception e) {
519: throw new IllegalArgumentException(e.toString());
520: }
521: }
522:
523: public boolean isExternalSequence() {
524: return externalSequence;
525: }
526:
527: public void setExternalSequence(boolean externalSequence) {
528: this .externalSequence = externalSequence;
529: }
530:
531: public Number getInitialValue() {
532: return initialValue;
533: }
534:
535: public void setInitialValue(Number initialValue) {
536: this .initialValue = initialValue;
537: }
538:
539: public boolean isCacheStatements() {
540: return cacheStatements;
541: }
542:
543: public void setCacheStatements(boolean cacheStatements) {
544: this .cacheStatements = cacheStatements;
545: }
546:
547: public boolean isCreateSequenceOnDemand() {
548: return createSequenceOnDemand;
549: }
550:
551: public void setCreateSequenceOnDemand(boolean createSequenceOnDemand) {
552: this .createSequenceOnDemand = createSequenceOnDemand;
553: }
554:
555: public void reset() throws SQLException {
556: table = null;
557: idColumn = null;
558: sequenceNameColumn = null;
559: increment = 10;
560: retryMax = 3;
561: timeout = 250;
562: vendorErrorCode = Integer.MIN_VALUE;
563: isolationLevel = -1;
564: isolationLevelSet = false;
565: lowValues.clear();
566: hiValues.clear();
567: keyClass = Integer.class;
568: externalSequence = false;
569: select = null;
570: update = null;
571: ;
572: insert = null;
573: initialValue = new Integer(0);
574: cacheStatements = false;
575: sequences.clear();
576: dbServices.release(true);
577: }
578:
579: }
|