KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > database > SequenceManager


1 /**
2  * $RCSfile: SequenceManager.java,v $
3  * $Revision: 1.7 $
4  * $Date: 2005/05/25 19:21:51 $
5  *
6  * Copyright (C) 2004 Jive Software. All rights reserved.
7  *
8  * This software is published under the terms of the GNU Public License (GPL),
9  * a copy of which is included in this distribution.
10  */

11
12 package org.jivesoftware.database;
13
14 import org.jivesoftware.util.JiveConstants;
15 import org.jivesoftware.util.Log;
16
17 import java.sql.Connection JavaDoc;
18 import java.sql.PreparedStatement JavaDoc;
19 import java.sql.ResultSet JavaDoc;
20 import java.sql.SQLException JavaDoc;
21 import java.util.Map JavaDoc;
22 import java.util.concurrent.ConcurrentHashMap JavaDoc;
23
24 /**
25  * Manages sequences of unique ID's that get stored in the database. Database support for sequences
26  * varies widely; some don't use them at all. Instead, we handle unique ID generation with a
27  * combination VM/database solution.<p>
28  * <p/>
29  * A special table in the database doles out blocks of unique ID's to each
30  * virtual machine that interacts with Jive. This has the following consequences:
31  * <ul>
32  * <li>There is no need to go to the database every time we want a new unique id.
33  * <li>Multiple app servers can interact with the same db without id collision.
34  * <li>The order of unique id's may not correspond to the creation date of objects.
35  * <li>There can be gaps in ID's after server restarts since blocks will get "lost" if the block
36  * size is greater than 1.
37  * </ul><p>
38  * <p/>
39  * Each sequence type that this class manages has a different block size value. Objects that aren't
40  * created often have a block size of 1, while frequently created objects such as entries and
41  * comments have larger block sizes.
42  *
43  * @author Matt Tucker
44  * @author Bruce Ritchie
45  */

46 public class SequenceManager {
47
48     private static final String JavaDoc LOAD_ID =
49             "SELECT id FROM jiveID WHERE idType=?";
50
51     private static final String JavaDoc UPDATE_ID =
52             "UPDATE jiveID SET id=? WHERE idType=? AND id=?";
53
54     private static final String JavaDoc VERIFY_TYPE = "SELECT 1 FROM jiveID WHERE idType = ?";
55
56     // Statically startup a sequence manager for each of the sequence counters.
57
private static Map JavaDoc<Integer JavaDoc,SequenceManager> managers = new ConcurrentHashMap JavaDoc<Integer JavaDoc,SequenceManager>();
58
59     static {
60         new SequenceManager(JiveConstants.ROSTER, 5);
61         new SequenceManager(JiveConstants.OFFLINE, 1);
62         new SequenceManager(JiveConstants.MUC_ROOM, 1);
63     }
64
65     /**
66      * Returns the next ID of the specified type.
67      *
68      * @param type the type of unique ID.
69      * @return the next unique ID of the specified type.
70      */

71     public static long nextID(int type) {
72         if (managers.containsKey(type)) {
73             return managers.get(type).nextUniqueID();
74         }
75         else {
76             // Verify type is valid from the db, if so create an instance for the type
77
// And return the next unique id
78
if(isValidType(type)) {
79                 SequenceManager manager = new SequenceManager(type, 1);
80                 return manager.nextUniqueID();
81             }
82
83             throw new IllegalArgumentException JavaDoc("Invalid type");
84         }
85     }
86
87     /**
88      * Method for objects that have defined the annotation {@link JiveID} in their class.
89      *
90      * The annotation JiveID should contain the id type for the object (the same number you would
91      * use to call nextID(int type))
92      *
93      * Example class definition:
94      *
95      * <code>
96      * \@JiveID(10)
97      * public class MyClass {
98      *
99      * }
100      * </code>
101      *
102      * @param o object that has annotation JiveID
103      * @return the next int
104      */

105     public static long nextID(Object JavaDoc o) {
106         JiveID id = o.getClass().getAnnotation(JiveID.class);
107
108         if(id == null) {
109             Log.error("Annotation JiveID must be defined in the class "+o.getClass());
110             throw new IllegalArgumentException JavaDoc(
111                     "Annotation JiveID must be defined in the class "+o.getClass());
112         }
113
114         return nextID(id.value());
115     }
116
117     /**
118      * Used to set the blocksize of a given SequenceManager. If no SequenceManager has been registered
119      * for the type we will verify the type is valid and then create a new sequence manager for it.
120      *
121      * @param type the type of unique id
122      * @param blockSize how many blocks of ids we should
123      */

124     public static void setBlockSize(int type, int blockSize) {
125         if (managers.containsKey(type)) {
126             managers.get(type).blockSize = blockSize;
127         }
128         else {
129             // Verify type is valid from the db, if so create an instance for the type
130
if(isValidType(type)) {
131                 new SequenceManager(type, blockSize);
132             } else {
133                 throw new IllegalArgumentException JavaDoc("Invalid type");
134             }
135         }
136     }
137
138     private int type;
139     private long currentID;
140     private long maxID;
141     private int blockSize;
142
143     /**
144      * Creates a new DbSequenceManager.
145      *
146      * @param seqType the type of sequence.
147      * @param size the number of id's to "checkout" at a time.
148      */

149     public SequenceManager(int seqType, int size) {
150         managers.put(seqType, this);
151         this.type = seqType;
152         this.blockSize = size;
153         currentID = 0l;
154         maxID = 0l;
155     }
156
157     /**
158      * Returns the next available unique ID. Essentially this provides for the functionality of an
159      * auto-increment database field.
160      */

161     public synchronized long nextUniqueID() {
162         if (!(currentID < maxID)) {
163             // Get next block -- make 5 attempts at maximum.
164
getNextBlock(5);
165         }
166         long id = currentID;
167         currentID++;
168         return id;
169     }
170
171     /**
172      * Performs a lookup to get the next available ID block. The algorithm is as follows:
173      * <ol>
174      * <li> Select currentID from appropriate db row.
175      * <li> Increment id returned from db.
176      * <li> Update db row with new id where id=old_id.
177      * <li> If update fails another process checked out the block first; go back to step 1.
178      * Otherwise, done.
179      * </ol>
180      */

181     private void getNextBlock(int count) {
182         if (count == 0) {
183             Log.error("Failed at last attempt to obtain an ID, aborting...");
184             return;
185         }
186
187         Connection JavaDoc con = null;
188         PreparedStatement JavaDoc pstmt = null;
189         boolean abortTransaction = false;
190         boolean success = false;
191
192         try {
193             con = DbConnectionManager.getTransactionConnection();
194             // Get the current ID from the database.
195
pstmt = con.prepareStatement(LOAD_ID);
196             pstmt.setInt(1, type);
197             ResultSet JavaDoc rs = pstmt.executeQuery();
198             if (!rs.next()) {
199                 throw new SQLException JavaDoc("Loading the current ID failed. The " +
200                         "jiveID table may not be correctly populated.");
201             }
202             long currentID = rs.getLong(1);
203             rs.close();
204             pstmt.close();
205
206             // Increment the id to define our block.
207
long newID = currentID + blockSize;
208             // The WHERE clause includes the last value of the id. This ensures
209
// that an update will occur only if nobody else has performed an
210
// update first.
211
pstmt = con.prepareStatement(UPDATE_ID);
212             pstmt.setLong(1, newID);
213             pstmt.setInt(2, type);
214             pstmt.setLong(3, currentID);
215             // Check to see if the row was affected. If not, some other process
216
// already changed the original id that we read. Therefore, this
217
// round failed and we'll have to try again.
218
success = pstmt.executeUpdate() == 1;
219             if (success) {
220                 this.currentID = currentID;
221                 this.maxID = newID;
222             }
223         }
224         catch (SQLException JavaDoc e) {
225             Log.error(e);
226             abortTransaction = true;
227         }
228         finally {
229             try { if (pstmt != null) { pstmt.close(); } }
230             catch (Exception JavaDoc e) { Log.error(e); }
231             DbConnectionManager.closeTransactionConnection(con, abortTransaction);
232         }
233
234         if (!success) {
235             Log.error("WARNING: failed to obtain next ID block due to " +
236                     "thread contention. Trying again...");
237             // Call this method again, but sleep briefly to try to avoid thread contention.
238
try {
239                 Thread.sleep(75);
240             }
241             catch (InterruptedException JavaDoc ie) {
242             }
243             getNextBlock(count - 1);
244         }
245     }
246
247
248     /**
249      * Checks to seed if the jiveID type listed is valid
250      *
251      * @param type jiveID type to check
252      * @return true if it is valid
253      */

254     private static boolean isValidType(int type) {
255
256         boolean isValid = false;
257
258         Connection JavaDoc con = null;
259         PreparedStatement JavaDoc pstmt = null;
260         try {
261             con = DbConnectionManager.getConnection();
262             pstmt = con.prepareStatement(VERIFY_TYPE);
263             pstmt.setInt(1, type);
264
265             ResultSet JavaDoc rs = pstmt.executeQuery();
266             if(rs.next()) {
267                 isValid = true;
268             }
269         }
270         catch (SQLException JavaDoc sqle) {
271             Log.error(sqle);
272         }
273         finally {
274             try { if (pstmt != null) { pstmt.close(); } }
275             catch (Exception JavaDoc e) { Log.error(e); }
276             try { if (con != null) { con.close(); } }
277             catch (Exception JavaDoc e) { Log.error(e); }
278         }
279
280         return isValid;
281     }
282
283 }
284
Popular Tags