001: /**
002: * $RCSfile$
003: * $Revision: 3826 $
004: * $Date: 2006-05-01 14:52:38 -0700 (Mon, 01 May 2006) $
005: *
006: * Copyright (C) 2004 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.database;
011:
012: import org.jivesoftware.util.JiveConstants;
013: import org.jivesoftware.util.Log;
014:
015: import java.sql.Connection;
016: import java.sql.PreparedStatement;
017: import java.sql.ResultSet;
018: import java.sql.SQLException;
019: import java.util.Map;
020: import java.util.concurrent.ConcurrentHashMap;
021:
022: /**
023: * Manages sequences of unique ID's that get stored in the database. Database support for sequences
024: * varies widely; some don't use them at all. Instead, we handle unique ID generation with a
025: * combination VM/database solution.<p/>
026: *
027: * A special table in the database doles out blocks of unique ID's to each
028: * virtual machine that interacts with Jive. This has the following consequences:
029: * <ul>
030: * <li>There is no need to go to the database every time we want a new unique id.
031: * <li>Multiple app servers can interact with the same db without id collision.
032: * <li>The order of unique id's may not correspond to the creation date of objects.
033: * <li>There can be gaps in ID's after server restarts since blocks will get "lost" if the block
034: * size is greater than 1.
035: * </ul><p/>
036: *
037: * Each sequence type that this class manages has a different block size value. Objects that aren't
038: * created often have a block size of 1, while frequently created objects such as entries and
039: * comments have larger block sizes.
040: *
041: * @author Matt Tucker
042: * @author Bruce Ritchie
043: */
044: public class SequenceManager {
045:
046: private static final String CREATE_ID = "INSERT INTO jiveID (id, idType) VALUES (1, ?)";
047:
048: private static final String LOAD_ID = "SELECT id FROM jiveID WHERE idType=?";
049:
050: private static final String UPDATE_ID = "UPDATE jiveID SET id=? WHERE idType=? AND id=?";
051:
052: // Statically startup a sequence manager for each of the sequence counters.
053: private static Map<Integer, SequenceManager> managers = new ConcurrentHashMap<Integer, SequenceManager>();
054:
055: static {
056: new SequenceManager(JiveConstants.ROSTER, 5);
057: new SequenceManager(JiveConstants.OFFLINE, 1);
058: new SequenceManager(JiveConstants.MUC_ROOM, 1);
059: }
060:
061: /**
062: * Returns the next ID of the specified type.
063: *
064: * @param type the type of unique ID.
065: * @return the next unique ID of the specified type.
066: */
067: public static long nextID(int type) {
068: if (managers.containsKey(type)) {
069: return managers.get(type).nextUniqueID();
070: } else {
071: // Verify type is valid from the db, if so create an instance for the type
072: // And return the next unique id
073: SequenceManager manager = new SequenceManager(type, 1);
074: return manager.nextUniqueID();
075: }
076: }
077:
078: /**
079: * Returns the next id for an object that has defined the annotation {@link JiveID}.
080: * The JiveID annotation value is the synonymous for the type integer.<p/>
081: *
082: * The annotation JiveID should contain the id type for the object (the same number you would
083: * use to call nextID(int type)). Example class definition:
084: * <code>
085: * \@JiveID(10)
086: * public class MyClass {
087: *
088: * }
089: * </code>
090: *
091: * @param o object that has annotation JiveID.
092: * @return the next unique ID.
093: * @throws IllegalArgumentException If the object passed in does not defined {@link JiveID}
094: */
095: public static long nextID(Object o) {
096: JiveID id = o.getClass().getAnnotation(JiveID.class);
097:
098: if (id == null) {
099: Log.error("Annotation JiveID must be defined in the class "
100: + o.getClass());
101: throw new IllegalArgumentException(
102: "Annotation JiveID must be defined in the class "
103: + o.getClass());
104: }
105:
106: return nextID(id.value());
107: }
108:
109: /**
110: * Used to set the blocksize of a given SequenceManager. If no SequenceManager has
111: * been registered for the type, the type is verified as valid and then a new
112: * sequence manager is created.
113: *
114: * @param type the type of unique id.
115: * @param blockSize how many blocks of ids we should.
116: */
117: public static void setBlockSize(int type, int blockSize) {
118: if (managers.containsKey(type)) {
119: managers.get(type).blockSize = blockSize;
120: } else {
121: new SequenceManager(type, blockSize);
122: }
123: }
124:
125: private int type;
126: private long currentID;
127: private long maxID;
128: private int blockSize;
129:
130: /**
131: * Creates a new DbSequenceManager.
132: *
133: * @param seqType the type of sequence.
134: * @param size the number of id's to "checkout" at a time.
135: */
136: public SequenceManager(int seqType, int size) {
137: managers.put(seqType, this );
138: this .type = seqType;
139: this .blockSize = size;
140: currentID = 0l;
141: maxID = 0l;
142: }
143:
144: /**
145: * Returns the next available unique ID. Essentially this provides for the functionality of an
146: * auto-increment database field.
147: */
148: public synchronized long nextUniqueID() {
149: if (!(currentID < maxID)) {
150: // Get next block -- make 5 attempts at maximum.
151: getNextBlock(5);
152: }
153: long id = currentID;
154: currentID++;
155: return id;
156: }
157:
158: /**
159: * Performs a lookup to get the next available ID block. The algorithm is as follows:
160: * <ol>
161: * <li> Select currentID from appropriate db row.
162: * <li> Increment id returned from db.
163: * <li> Update db row with new id where id=old_id.
164: * <li> If update fails another process checked out the block first; go back to step 1.
165: * Otherwise, done.
166: * </ol>
167: */
168: private void getNextBlock(int count) {
169: if (count == 0) {
170: Log
171: .error("Failed at last attempt to obtain an ID, aborting...");
172: return;
173: }
174:
175: Connection con = null;
176: PreparedStatement pstmt = null;
177: boolean abortTransaction = false;
178: boolean success = false;
179:
180: try {
181: con = DbConnectionManager.getTransactionConnection();
182: // Get the current ID from the database.
183: pstmt = con.prepareStatement(LOAD_ID);
184: pstmt.setInt(1, type);
185: ResultSet rs = pstmt.executeQuery();
186:
187: long currentID = 1;
188: if (!rs.next()) {
189: rs.close();
190: pstmt.close();
191:
192: createNewID(con, type);
193: } else {
194: currentID = rs.getLong(1);
195: rs.close();
196: pstmt.close();
197: }
198:
199: // Increment the id to define our block.
200: long newID = currentID + blockSize;
201: // The WHERE clause includes the last value of the id. This ensures
202: // that an update will occur only if nobody else has performed an
203: // update first.
204: pstmt = con.prepareStatement(UPDATE_ID);
205: pstmt.setLong(1, newID);
206: pstmt.setInt(2, type);
207: pstmt.setLong(3, currentID);
208: // Check to see if the row was affected. If not, some other process
209: // already changed the original id that we read. Therefore, this
210: // round failed and we'll have to try again.
211: success = pstmt.executeUpdate() == 1;
212: if (success) {
213: this .currentID = currentID;
214: this .maxID = newID;
215: }
216: } catch (SQLException e) {
217: Log.error(e);
218: abortTransaction = true;
219: } finally {
220: try {
221: if (pstmt != null) {
222: pstmt.close();
223: }
224: } catch (Exception e) {
225: Log.error(e);
226: }
227: DbConnectionManager.closeTransactionConnection(con,
228: abortTransaction);
229: }
230:
231: if (!success) {
232: Log.error("WARNING: failed to obtain next ID block due to "
233: + "thread contention. Trying again...");
234: // Call this method again, but sleep briefly to try to avoid thread contention.
235: try {
236: Thread.sleep(75);
237: } catch (InterruptedException ie) {
238: // Ignore.
239: }
240: getNextBlock(count - 1);
241: }
242: }
243:
244: private void createNewID(Connection con, int type)
245: throws SQLException {
246: Log.warn("Autocreating jiveID row for type '" + type + "'");
247:
248: // create new ID row
249: PreparedStatement pstmt = null;
250:
251: try {
252: pstmt = con.prepareStatement(CREATE_ID);
253: pstmt.setInt(1, type);
254: pstmt.execute();
255: } finally {
256: DbConnectionManager.closeConnection(pstmt, null);
257: }
258: }
259: }
|