001: /* Copyright 2001, 2002 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal;
007:
008: import java.sql.Connection;
009: import java.sql.PreparedStatement;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.sql.Statement;
013: import java.util.Random;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017:
018: /**
019: * @author Dan Ellentuck
020: * @version $Revision: 36408 $
021: */
022: public class ReferenceSequenceGenerator implements ISequenceGenerator {
023:
024: private static final Log log = LogFactory
025: .getLog(ReferenceSequenceGenerator.class);
026:
027: /*
028: * Private exception identifies problem incrementing counter likely
029: * because of concurrent access.
030: */
031: private class DataIntegrityException extends SQLException {
032: DataIntegrityException(String msg) {
033: super (msg);
034: }
035: }
036:
037: private Random rand = new Random();
038:
039: // Constant strings for SEQUENCE table:
040: private static String SEQUENCE_TABLE = "UP_SEQUENCE";
041: private static String NAME_COLUMN = "SEQUENCE_NAME";
042: private static String VALUE_COLUMN = "SEQUENCE_VALUE";
043: private static int INITIAL_COUNTER_VALUE = 0;
044: private static int NO_COUNTER_VALUE = -1;
045:
046: // SQL strings for SEQUENCE crud:
047: private static String QUOTE = "'";
048: private static String EQ = " = ";
049: private static String DEFAULT_COUNTER_NAME = "DEFAULT";
050: private static String selectCounterSql;
051: private static String updateCounterSql;
052: private static String updateCounterForIncrementSql;
053:
054: /**
055: * ReferenceOIDGenerator constructor comment.
056: */
057: public ReferenceSequenceGenerator() {
058: super ();
059: }
060:
061: /**
062: * @param tableName java.lang.String
063: * @exception SQLException
064: */
065: public synchronized void createCounter(String tableName)
066: throws SQLException {
067: Connection conn = null;
068: try {
069: conn = RDBMServices.getConnection();
070: createCounter(tableName, conn);
071: } finally {
072: RDBMServices.releaseConnection(conn);
073: }
074: }
075:
076: /**
077: * @param tableName java.lang.String
078: * @exception SQLException
079: */
080: private void createCounter(String tableName, Connection conn)
081: throws SQLException {
082: Statement stmt = null;
083: try {
084: stmt = conn.createStatement();
085: String sql = getCreateCounterSql(tableName);
086:
087: if (log.isDebugEnabled())
088: log.debug("ReferenceSequenceGenerator.createCounter: "
089: + sql);
090:
091: int rc = stmt.executeUpdate(getCreateCounterSql(tableName));
092: if (rc != 1) {
093: throw new DataIntegrityException(
094: "Data integrity error; could not update counter.");
095: }
096:
097: } catch (SQLException sqle) {
098: log.error("ReferenceSequenceGenerator::createCounter()",
099: sqle);
100: throw sqle;
101: } finally {
102: if (stmt != null) {
103: stmt.close();
104: }
105: }
106: }
107:
108: /**
109: * @param table java.lang.String
110: * @return java.lang.String
111: */
112: private String getCreateCounterSql(String table) {
113: StringBuffer buff = new StringBuffer(100);
114: buff.append("INSERT INTO ");
115: buff.append(SEQUENCE_TABLE);
116: buff.append(" (");
117: buff.append(NAME_COLUMN);
118: buff.append(", ");
119: buff.append(VALUE_COLUMN);
120: buff.append(") VALUES (");
121: buff.append(sqlQuote(table));
122: buff.append(", ");
123: buff.append(INITIAL_COUNTER_VALUE);
124: buff.append(")");
125: return buff.toString();
126: }
127:
128: /**
129: * @return int
130: * @param tableName java.lang.String
131: * @param conn java.sql.Connection
132: * @exception java.sql.SQLException
133: */
134: private int getCurrentCounterValue(String tableName, Connection conn)
135: throws SQLException {
136: ResultSet rs = null;
137: PreparedStatement ps = null;
138: try {
139: ps = conn.prepareStatement(getSelectCounterSql());
140: try {
141: ps.setString(1, tableName);
142: if (log.isDebugEnabled())
143: log
144: .debug("ReferenceSequenceGenerator.getNextInt(): "
145: + ps + " (" + tableName + ")");
146: rs = ps.executeQuery();
147: int currentInt = (rs.next()) ? rs.getInt(VALUE_COLUMN)
148: : NO_COUNTER_VALUE;
149: return currentInt;
150: } finally {
151: if (rs != null) {
152: rs.close();
153: }
154: }
155: }
156:
157: finally {
158: if (ps != null) {
159: ps.close();
160: }
161: }
162: }
163:
164: /**
165: * @return java.lang.String
166: * @exception java.lang.Exception
167: */
168: public String getNext() throws Exception {
169: return getNext(DEFAULT_COUNTER_NAME);
170: }
171:
172: /**
173: * @param table String
174: * @return java.lang.String
175: * @exception java.lang.Exception
176: */
177: public String getNext(String table) throws Exception {
178: return getNextInt(table) + "";
179: }
180:
181: /**
182: * @return int
183: * @exception java.lang.Exception
184: */
185: public int getNextInt() throws Exception {
186: return getNextInt(DEFAULT_COUNTER_NAME);
187: }
188:
189: /**
190: * Increments the counter and returns the incremented value. If the counter
191: * does not exist, creates and then increments it to verify that it has been
192: * created successfully.
193: * @return int
194: * @param tableName java.lang.String
195: * @exception java.lang.Exception
196: */
197: public synchronized int getNextInt(String tableName)
198: throws Exception {
199: Connection conn = null;
200: try {
201: conn = RDBMServices.getConnection();
202: int current = getCurrentCounterValue(tableName, conn);
203:
204: if (current == NO_COUNTER_VALUE) {
205: createCounter(tableName, conn);
206: current = INITIAL_COUNTER_VALUE;
207: }
208:
209: return incrementCounter(tableName, current, conn);
210: }
211:
212: catch (SQLException sqle) {
213: log.error("ReferenceSequenceGenerator.getNextInt()", sqle);
214: throw sqle;
215: }
216:
217: finally {
218: RDBMServices.releaseConnection(conn);
219: }
220: }
221:
222: /**
223: * @return java.lang.String
224: */
225: private String getSelectCounterSql() {
226: if (selectCounterSql == null) {
227: selectCounterSql = "SELECT " + VALUE_COLUMN + " FROM "
228: + SEQUENCE_TABLE + " WHERE " + NAME_COLUMN + EQ
229: + "?";
230: }
231: return selectCounterSql;
232: }
233:
234: /**
235: * @return java.lang.String
236: */
237: private String getUpdateCounterForIncrementSql() {
238: if (updateCounterForIncrementSql == null) {
239: updateCounterForIncrementSql = "UPDATE " + SEQUENCE_TABLE
240: + " SET " + VALUE_COLUMN + EQ + " ? " + " WHERE "
241: + NAME_COLUMN + EQ + "? AND " + VALUE_COLUMN + EQ
242: + "?";
243: }
244: return updateCounterForIncrementSql;
245: }
246:
247: /**
248: * @return java.lang.String
249: */
250: private String getUpdateCounterSql() {
251: if (updateCounterSql == null) {
252: updateCounterSql = "UPDATE " + SEQUENCE_TABLE + " SET "
253: + VALUE_COLUMN + EQ + " ? " + " WHERE "
254: + NAME_COLUMN + EQ + "?";
255: }
256: return updateCounterSql;
257: }
258:
259: /**
260: * Try to increment the counter for <code>tableName</code>. If we catch a
261: * <code>DataIntegrityException</code> -- which probably means some other
262: * process is trying to increment the counter at the same time -- sleep
263: * for a while and then try again, up to 20 times.
264: *
265: * @param tableName java.lang.String
266: * @param currentCounterValue
267: * @param conn java.sql.Connection
268: * @exception java.sql.SQLException
269: */
270: private int incrementCounter(String tableName,
271: int currentCounterValue, Connection conn) throws Exception {
272: int current = currentCounterValue;
273: boolean incremented = false;
274: for (int i = 0; i < 20 && !incremented; i++) {
275: try {
276: primIncrementCounter(tableName, current, conn);
277: incremented = true;
278: } catch (DataIntegrityException die) {
279: Thread.sleep(rand.nextInt(2000));
280: current = getCurrentCounterValue(tableName, conn);
281: }
282: }
283: if (incremented) {
284: return ++current;
285: } else {
286: throw new DataIntegrityException(
287: "Could not increment counter.");
288: }
289: }
290:
291: /**
292: * @param tableName java.lang.String
293: * @param currentCounterValue
294: * @param conn java.sql.Connection
295: * @exception java.sql.SQLException
296: */
297: private void primIncrementCounter(String tableName,
298: int currentCounterValue, Connection conn)
299: throws SQLException {
300: PreparedStatement ps = null;
301: int nextCounterValue = currentCounterValue + 1;
302: try {
303: ps = conn
304: .prepareStatement(getUpdateCounterForIncrementSql());
305: try {
306: ps.setInt(1, nextCounterValue);
307: ps.setString(2, tableName);
308: ps.setInt(3, currentCounterValue);
309: if (log.isDebugEnabled())
310: log
311: .debug("ReferenceSequenceGenerator.primIncrementCounter(): "
312: + ps
313: + "("
314: + nextCounterValue
315: + ", "
316: + tableName
317: + ", "
318: + currentCounterValue + ")");
319: int rc = ps.executeUpdate();
320: if (rc != 1) {
321: throw new DataIntegrityException(
322: "Data integrity error; could not update counter: "
323: + tableName
324: + " currentCounterValue:"
325: + currentCounterValue);
326: }
327: } catch (SQLException sqle) {
328: log.error(sqle.getMessage(), sqle);
329: throw sqle;
330: }
331: }
332:
333: finally {
334: if (ps != null) {
335: ps.close();
336: }
337: }
338: }
339:
340: /**
341: * @param tableName java.lang.String
342: * @param newCounterValue int
343: * @exception java.lang.Exception
344: */
345: public synchronized void setCounter(String tableName,
346: int newCounterValue) throws Exception {
347:
348: Connection conn = null;
349: try {
350: conn = RDBMServices.getConnection();
351: setCounter(tableName, newCounterValue, conn);
352: }
353:
354: catch (SQLException sqle) {
355: log.error("ReferenceSequenceGenerator::setCounter()", sqle);
356: throw sqle;
357: }
358:
359: finally {
360: RDBMServices.releaseConnection(conn);
361: }
362: }
363:
364: /**
365: * @param tableName java.lang.String
366: * @param newCounterValue
367: * @param conn java.sql.Connection
368: * @exception java.sql.SQLException
369: */
370: private void setCounter(String tableName, int newCounterValue,
371: Connection conn) throws SQLException {
372: PreparedStatement ps = null;
373: try {
374: ps = conn.prepareStatement(getUpdateCounterSql());
375: try {
376: ps.setInt(1, newCounterValue);
377: ps.setString(2, tableName);
378: if (log.isDebugEnabled())
379: log
380: .debug("ReferenceSequenceGenerator.setCounter(): "
381: + ps
382: + "("
383: + newCounterValue
384: + ", "
385: + tableName + ")");
386: int rc = ps.executeUpdate();
387: if (rc != 1) {
388: throw new SQLException(
389: "Data integrity error; could not update counter.");
390: }
391: } catch (SQLException sqle) {
392: log.error("Error setting counter for table ["
393: + tableName + "] " + "to " + newCounterValue,
394: sqle);
395: throw sqle;
396: }
397: }
398:
399: finally {
400: if (ps != null) {
401: ps.close();
402: }
403: }
404: }
405:
406: /**
407: * @return java.lang.String
408: */
409: private static java.lang.String sqlQuote(Object o) {
410: return QUOTE + o + QUOTE;
411: }
412: }
|