Source code: com/lutris/appserver/server/sql/standard/StandardObjectIdAllocator.java
1 /*
2 * Enhydra Java Application Server Project
3 *
4 * The contents of this file are subject to the Enhydra Public License
5 * Version 1.1 (the "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy of the License on
7 * the Enhydra web site ( http://www.enhydra.org/ ).
8 *
9 * Software distributed under the License is distributed on an "AS IS"
10 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 * the License for the specific terms governing rights and limitations
12 * under the License.
13 *
14 * The Initial Developer of the Enhydra Application Server is Lutris
15 * Technologies, Inc. The Enhydra Application Server and portions created
16 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17 * All Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * $Id: StandardObjectIdAllocator.java,v 1.7.12.1 2000/10/19 17:59:06 jasona Exp $
22 */
23
24
25
26
27
28 package com.lutris.appserver.server.sql.standard;
29
30 import com.lutris.appserver.server.sql.*;
31 import com.lutris.appserver.server.LBS;
32 import java.util.Hashtable;
33 import java.math.BigDecimal;
34 import java.sql.*;
35 import com.lutris.logging.*;
36 import com.lutris.util.*;
37
38
39 /**
40 * Object ids can only be created via this manager.
41 * Ensures that all object ids are unique across all
42 * objects in this logical database. Also ensures good performance
43 * for allocating object ids.
44 * <P>
45 * The configuration data is specified in the section:
46 * <CODE>DatabaseManager.DB.<I>dbName</I>.ObjectId</CODE>
47 * <P>
48 * Configuration fields are:
49 * <UL>
50 * <LI> <CODE>CacheSize</CODE> -
51 * The number of object id's to cache between database queries. Optional,
52 * if not specified, then it defaults to 1024.
53 * <LI> <CODE>MinValue</CODE> -
54 * The starting number of Object ID allocation. This will only be used
55 * if the Object ID table is empty and thus is useful in development
56 * and testing. Optional, if not specified it defaults to
57 * 100000000000000000. Note that the largest number that can be
58 * associated with an OID in LBS is "database: DECIMAL(19,0)"
59 * </UL>
60 *
61 * @author Kyle Clark
62 * @author Paul Morgan
63 * @since LBS1.8
64 * @version $Revision: 1.7.12.1 $
65 */
66 public class StandardObjectIdAllocator implements ObjectIdAllocator {
67
68 /**
69 * The maximum object id in the cache.
70 */
71 private ObjectId max = null;
72
73 /**
74 * The object id mos recently allocated.
75 */
76 private ObjectId current = null;
77
78 /**
79 * The next object id that will be allocated.
80 */
81 private ObjectId next = null;
82
83 /**
84 * Default cache size if 1K. Note that if the
85 * application is started and stopped frequently
86 * you should drop this value in order not
87 * to waste object ids.
88 */
89 private long cacheSize = 1024;
90
91 /**
92 * Starting value for object id creation, if table is empty.
93 * This is a string because very large numbers are possible.
94 */
95 private String oidMinValue = "0";
96
97 /**
98 * Reference to the logical database for easy access to the
99 * connection pool.
100 */
101 private LogicalDatabase logicalDatabase = null;
102
103 /**
104 * If allocation of object id's fails (because the contents of
105 * the table are modified by another process) this object
106 * id manager will keep trying to allocate object ids
107 * until it succeeds. If it fails more than LOG_THRESHOLD
108 * times, then it will log a message to the system.
109 */
110 private static final int LOG_THRESHOLD = 100;
111
112 /**
113 * The log channel.
114 */
115 private LogChannel log = LBS.getLogChannel();
116
117
118 /**
119 * Initialize the object id manager.
120 *
121 * @param objCacheSize The number of object ids to cache between
122 * database queries.
123 * @param objIdMinValue The starting number for object ids. Used
124 * only if object id table is empty.
125 * @exception ConfigException if bad configuration information is
126 * given in the config file.
127 */
128 protected StandardObjectIdAllocator (LogicalDatabase logicalDatabase,
129 Config objIdConfig)
130 throws ConfigException
131 {
132 this.logicalDatabase = logicalDatabase;
133
134 try {
135 cacheSize = objIdConfig.getInt ("CacheSize", 1024);
136 oidMinValue = objIdConfig.getString ("MinValue", "10000000000000000");
137 } catch (KeywordValueException except) {
138 throw new ConfigException ("Bad DatabaseManager.DB." + logicalDatabase.getName() + ".ObjectId section defined in config file.");
139 }
140 }
141
142
143 /**
144 * Allocates a new object id.
145 */
146 public synchronized ObjectId allocate () {
147
148 try {
149 if (next == null || max == null || next.equals(max)) {
150 updateCache();
151 }
152 current = next;
153 next = next.increment();
154 return current;
155 } catch (Exception e) {
156 throw new ObjectIdAllocationError(
157 "ObjectIdAllocator: " +
158 "\nFailed to allocate object id. Caused by: " +
159 "\n" + e.getMessage());
160 }
161 }
162
163
164 /**
165 * Updates the cache of object id's.
166 */
167 private void updateCache() {
168
169 final String OID_TABLE = "objectid";
170 DBConnection conn = null;
171
172 try {
173 boolean tryAgain = false;
174 int tryCount = 0;
175
176 conn = logicalDatabase.allocateConnection();
177 conn.setAutoCommit(false);
178
179 do {
180 BigDecimal dbNext;
181 ResultSet rs;
182
183 // Query next available object id from database.
184 rs = conn.executeQuery("select * from " + OID_TABLE);
185 if (!rs.next()) {
186 // Table is empty - initialize
187 conn.executeUpdate("insert into " + OID_TABLE +
188 " values(" + oidMinValue + ")");
189 dbNext = new BigDecimal(oidMinValue);
190 }
191 else {
192 dbNext = rs.getBigDecimal("next", 0);
193 }
194 rs.close(); // In case there's more // JOHN
195
196 // Sync this managers next oid w/ current value in db.
197 next = new ObjectId(dbNext);
198
199 // Update db contents
200 // i.e. Allocate a block of oids of size cacheSize.
201 BigDecimal dbLast = dbNext;
202 dbNext = dbNext.add(BigDecimal.valueOf(cacheSize));
203 // This now updates all rows if there's more than one.
204 String sql =
205 "update " + OID_TABLE + " \n" +
206 "set next = ?";
207 //"where next = ?";
208 PreparedStatement stmt = conn.prepareStatement(sql);
209 stmt.setBigDecimal(1, dbNext);
210 //stmt.setBigDecimal(2, dbLast);
211 if (conn.executeUpdate(stmt, sql) < 1) {
212 // We were unable to update the table.
213 tryAgain = true;
214 tryCount++;
215 if (tryCount >= LOG_THRESHOLD) {
216 log.write(Logger.ALERT,
217 "ObjectIdAllocator: " +
218 "\n Failed to allocate object ids." +
219 "\n Trying again....");
220 }
221 // Transaction rollback
222 conn.rollback();
223 conn.reset(); // free resources
224 //conn.setLockModeToWait(true);
225 conn.setAutoCommit(false);
226 if (tryCount >= 50) {
227 log.write(Logger.ALERT,
228 "ObjectIdAllocator: " +
229 "\n Failed to allocate object ids." +
230 "\n Tried 50 times. Giving up.");
231 throw new ObjectIdAllocationError("Failed to allocate object id." +
232 "\nTried 50 times. Giving up.");
233 }
234 }
235 else {
236 tryAgain = false;
237
238 // Sync this managers max oid w/ db contents.
239 max = new ObjectId(dbNext);
240 // Transcation commit.
241 conn.commit();
242
243 if (tryCount >= LOG_THRESHOLD) {
244 log.write(Logger.ALERT,
245 "ObjectIdAllocator: " +
246 "\n Succeeded in allocating object ids." +
247 "\n Continuing...");
248 }
249 }
250 } while (tryAgain);
251 } catch (SQLException sqlExcept) {
252 // An error occured. Rollback all changes.
253 max = next; // The cache is not updated.
254
255 log.write(Logger.ALERT,
256 "ObjectIdAllocator: " +
257 "\n Failed to allocate object ids. Giving up!" +
258 "\n SQLState = " + sqlExcept.getSQLState() +
259 "\n SQLError = " + sqlExcept.getErrorCode() +
260 "\n SQLMsg = " + sqlExcept.getMessage());
261
262 if (conn != null) {
263 try {
264 conn.rollback();
265 } catch (SQLException rollbackExcept) {
266 log.write(Logger.ALERT,
267 "ObjectIdAllocator: " +
268 "\n Failed to rollback transaction." +
269 "\n SQLState = " + rollbackExcept.getSQLState() +
270 "\n SQLError = " + rollbackExcept.getErrorCode() +
271 "\n SQLMsg = " + rollbackExcept.getMessage());
272 }
273 if (!conn.handleException(sqlExcept))
274 conn = null;
275 }
276 throw new ObjectIdAllocationError(
277 "ObjectIdAllocator: " +
278 "\n Failed to allocate object id. Caused by SQLException:" +
279 "\n SQLState = " + sqlExcept.getSQLState() +
280 "\n SQLError = " + sqlExcept.getErrorCode() +
281 "\n SQLMsg = " + sqlExcept.getMessage());
282
283 } catch (ObjectIdException oidEx) {
284 throw new ObjectIdAllocationError(
285 "ObjectIdAllocator: " +
286 "\nFailed to allocate object id. Caused by: " +
287 "\n" + oidEx.getMessage());
288 } finally {
289 if (conn != null) {
290 try {
291 conn.reset(); // free existing resource if any
292 } catch (SQLException sqlExcept) {
293 log.write(Logger.ALERT,
294 "ObjectIdAllocator: " +
295 "\n Failed to reset connection." +
296 "\n SQLState = " + sqlExcept.getSQLState() +
297 "\n SQLError = " + sqlExcept.getErrorCode() +
298 "\n SQLMsg = " + sqlExcept.getMessage());
299 } finally {
300 conn.release();
301 }
302 }
303 }
304 }
305 }