KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > je > Sequence


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2005,2006 Oracle. All rights reserved.
5  *
6  * $Id: Sequence.java,v 1.8 2006/10/30 21:14:12 bostic Exp $
7  */

8
9 package com.sleepycat.je;
10
11 import java.math.BigInteger JavaDoc;
12 import java.nio.ByteBuffer JavaDoc;
13 import java.util.logging.Level JavaDoc;
14 import java.util.logging.Logger JavaDoc;
15
16 import com.sleepycat.je.log.LogUtils;
17 import com.sleepycat.je.txn.Locker;
18 import com.sleepycat.je.txn.LockerFactory;
19
20 /**
21  * Javadoc for this public class is generated via
22  * the doc templates in the doc_src directory.
23  */

24 public class Sequence {
25
26     private static final byte FLAG_INCR = ((byte) 0x1);
27     private static final byte FLAG_WRAP = ((byte) 0x2);
28     private static final byte FLAG_OVER = ((byte) 0x4);
29
30     /* Allocation size for the record data. */
31     private static final int MAX_DATA_SIZE = 50;
32
33     /* Version of the format for fields stored in the sequence record. */
34     private static final byte CURRENT_VERSION = 0;
35
36     /* A sequence is a unique record in a database. */
37     private Database db;
38     private DatabaseEntry key;
39
40     /* Persistent fields. */
41     private boolean wrapAllowed;
42     private boolean increment;
43     private boolean overflow;
44     private long rangeMin;
45     private long rangeMax;
46     private long storedValue;
47
48     /* Handle-specific fields. */
49     private int cacheSize;
50     private long cacheValue;
51     private long cacheLast;
52     private int nGets;
53     private int nCachedGets;
54     private TransactionConfig autoCommitConfig;
55     private Logger JavaDoc logger;
56
57     /*
58      * The cache holds the range of values [cacheValue, cacheLast], which is
59      * the same as [cacheValue, storedValue) at the time the record is written.
60      * At store time, cacheLast is set to one before (after) storedValue.
61      *
62      * storedValue may be used by other Sequence handles with separate caches.
63      * storedValue is always the next value to be returned by any handle that
64      * runs out of cached values.
65      */

66
67     /**
68      * Opens a sequence handle, adding the sequence record if appropriate.
69      */

70     Sequence(Database db,
71              Transaction txn,
72              DatabaseEntry key,
73              SequenceConfig config)
74         throws DatabaseException {
75
76         if (db.getDatabaseImpl().getSortedDuplicates()) {
77             throw new IllegalArgumentException JavaDoc
78                 ("Sequences not supported in databases configured for " +
79                  "duplicates");
80         }
81
82         SequenceConfig useConfig = (config != null) ?
83             config : SequenceConfig.DEFAULT;
84
85         if (useConfig.getRangeMin() >= useConfig.getRangeMax()) {
86             throw new IllegalArgumentException JavaDoc
87                 ("Minimum sequence value must be less than the maximum");
88         }
89
90         if (useConfig.getInitialValue() > useConfig.getRangeMax() ||
91             useConfig.getInitialValue() < useConfig.getRangeMin()) {
92             throw new IllegalArgumentException JavaDoc
93                 ("Initial sequence value is out of range");
94         }
95
96         if (useConfig.getRangeMin() >
97             useConfig.getRangeMax() - useConfig.getCacheSize()) {
98             throw new IllegalArgumentException JavaDoc
99                 ("The cache size is larger than the sequence range");
100         }
101
102         if (useConfig.getAutoCommitNoSync()) {
103             autoCommitConfig = new TransactionConfig();
104             autoCommitConfig.setNoSync(true);
105         } else {
106             /* Use the environment's default transaction config. */
107             autoCommitConfig = null;
108         }
109
110         this.db = db;
111         this.key = copyEntry(key);
112         logger = db.getEnvironment().getEnvironmentImpl().getLogger();
113
114         /* Perform an auto-commit transaction to create the sequence. */
115         Locker locker = null;
116         Cursor cursor = null;
117         OperationStatus status = OperationStatus.NOTFOUND;
118         try {
119             locker = LockerFactory.getWritableLocker
120                 (db.getEnvironment(), txn, db.isTransactional(),
121                  false, autoCommitConfig);
122
123             cursor = new Cursor(db, locker, null);
124
125             if (useConfig.getAllowCreate()) {
126
127                 /* Get the persistent fields from the config. */
128                 rangeMin = useConfig.getRangeMin();
129                 rangeMax = useConfig.getRangeMax();
130                 increment = !useConfig.getDecrement();
131                 wrapAllowed = useConfig.getWrap();
132                 storedValue = useConfig.getInitialValue();
133
134                 /*
135                  * To avoid dependence on SerializableIsolation, try
136                  * putNoOverwrite first. If it fails, then try to get an
137                  * existing record.
138                  */

139                 status = cursor.putNoOverwrite(key, makeData());
140
141                 if (status == OperationStatus.KEYEXIST) {
142                     if (useConfig.getExclusiveCreate()) {
143                         throw new DatabaseException
144                             ("ExclusiveCreate=true and the sequence record " +
145                              "already exists.");
146                     }
147                     if (!readData(cursor, null)) {
148                         throw new DatabaseException
149                             ("Sequence record removed during openSequence.");
150                     }
151                     status = OperationStatus.SUCCESS;
152                 }
153             } else {
154
155                 /* Get an existing record. */
156                 if (!readData(cursor, null)) {
157                     throw new DatabaseException
158                         ("AllowCreate=false and the sequence record " +
159                          "does not exist.");
160                 }
161                 status = OperationStatus.SUCCESS;
162             }
163         } finally {
164             if (cursor != null) {
165                 cursor.close();
166             }
167             if (locker != null) {
168                 locker.operationEnd(status);
169             }
170         }
171
172         /*
173          * cacheLast is initialized such that the cache will be considered
174          * empty the first time get() is called.
175          */

176         cacheSize = useConfig.getCacheSize();
177         cacheValue = storedValue;
178         cacheLast = increment ? (storedValue - 1) : (storedValue + 1);
179     }
180
181     /**
182      * Javadoc for this public method is generated via
183      * the doc templates in the doc_src directory.
184      */

185     public void close()
186         throws DatabaseException {
187
188         /* Defined only for DB compatibility and possible future use. */
189     }
190
191     /**
192      * Javadoc for this public method is generated via
193      * the doc templates in the doc_src directory.
194      *
195      * <p>This method is synchronized to protect updating of the cached value,
196      * since multiple threads may share a single handle. Multiple handles
197      * for the same database/key may be used to increase concurrency.</p>
198      */

199     public synchronized long get(Transaction txn, int delta)
200         throws DatabaseException {
201
202         /* Check parameters, being careful of overflow. */
203         if (delta <= 0) {
204             throw new IllegalArgumentException JavaDoc
205                 ("Sequence delta must be greater than zero");
206         }
207         if (rangeMin > rangeMax - delta) {
208             throw new IllegalArgumentException JavaDoc
209                 ("Sequence delta is larger than the range");
210         }
211
212         /* Status variables for tracing. */
213         boolean cached = true;
214         boolean wrapped = false;
215
216         /*
217          * Determine whether we have exceeded the cache. The cache size is
218          * always <= Integer.MAX_VALUE, so we don't have to worry about
219          * overflow here as long as we subtract the two long values first.
220          */

221         if ((increment && delta > ((cacheLast - cacheValue) + 1)) ||
222             (!increment && delta > ((cacheValue - cacheLast) + 1))) {
223
224             cached = false;
225
226             /*
227              * We need to allocate delta or cacheSize values, whichever is
228              * larger, by incrementing or decrementing the stored value by
229              * adjust.
230              */

231             int adjust = (delta > cacheSize) ? delta : cacheSize;
232
233             /* Perform an auto-commit transaction to update the sequence. */
234             Locker locker = null;
235             Cursor cursor = null;
236             OperationStatus status = OperationStatus.NOTFOUND;
237             try {
238                 locker = LockerFactory.getWritableLocker
239                     (db.getEnvironment(), txn, db.isTransactional(),
240                      false, autoCommitConfig);
241
242                 cursor = new Cursor(db, locker, null);
243
244                 /* Get the existing record. */
245                 readDataRequired(cursor, LockMode.RMW);
246
247                 /* If we would have wrapped when not allowed, overflow. */
248                 if (overflow) {
249                     throw new DatabaseException
250                         ("Sequence overflow " + storedValue);
251                 }
252
253                 /*
254                  * Handle wrapping. The range size can be larger than a long
255                  * can hold, so to avoid arithmetic overflow we use BigInteger
256                  * arithmetic. Since we are going to write, the BigInteger
257                  * overhead is acceptable.
258                  */

259                 BigInteger JavaDoc availBig;
260                 if (increment) {
261                     /* Available amount: rangeMax - storedValue */
262                     availBig = BigInteger.valueOf(rangeMax).
263                         subtract(BigInteger.valueOf(storedValue));
264                 } else {
265                     /* Available amount: storedValue - rangeMin */
266                     availBig = BigInteger.valueOf(storedValue).
267                         subtract(BigInteger.valueOf(rangeMin));
268                 }
269
270                 if (availBig.compareTo(BigInteger.valueOf(adjust)) < 0) {
271                     /* If availBig < adjust then availBig fits in an int. */
272                     int availInt = (int) availBig.longValue();
273                     if (availInt < delta) {
274                         if (wrapAllowed) {
275                             /* Wrap to the opposite range end point. */
276                             storedValue = increment ? rangeMin : rangeMax;
277                             wrapped = true;
278                         } else {
279                             /* Signal an overflow next time. */
280                             overflow = true;
281                             adjust = 0;
282                         }
283                     } else {
284
285                         /*
286                          * If the delta fits in the cache available, don't wrap
287                          * just to allocate the full cacheSize; instead,
288                          * allocate as much as is available.
289                          */

290                         adjust = availInt;
291                     }
292                 }
293
294                 /* Negate the adjustment for decrementing. */
295                 if (!increment) {
296                     adjust = -adjust;
297                 }
298
299                 /* Set the stored value one past the cached amount. */
300                 storedValue += adjust;
301
302                 /* Write the new stored value. */
303                 cursor.put(key, makeData());
304                 status = OperationStatus.SUCCESS;
305             } finally {
306                 if (cursor != null) {
307                     cursor.close();
308                 }
309                 if (locker != null) {
310                     locker.operationEnd(status);
311                 }
312             }
313
314             /* The cache now contains the range: [cacheValue, storedValue) */
315             cacheValue = storedValue - adjust;
316             cacheLast = storedValue + (increment ? (-1) : 1);
317         }
318
319         /* Return the current value and increment/decrement it by delta. */
320         long retVal = cacheValue;
321         if (increment) {
322             cacheValue += delta;
323         } else {
324             cacheValue -= delta;
325         }
326
327         /* Increment stats. */
328         nGets += 1;
329         if (cached) {
330             nCachedGets += 1;
331         }
332
333         /* Trace this method at the FINEST level. */
334         if (logger.isLoggable(Level.FINEST)) {
335             logger.log
336                 (Level.FINEST,
337                  "Sequence.get" +
338                  " value=" + retVal +
339                  " cached=" + cached +
340                  " wrapped=" + wrapped);
341         }
342
343         return retVal;
344     }
345
346     /**
347      * Javadoc for this public method is generated via
348      * the doc templates in the doc_src directory.
349      */

350     public Database getDatabase()
351         throws DatabaseException {
352
353         return db;
354     }
355
356     /**
357      * Javadoc for this public method is generated via
358      * the doc templates in the doc_src directory.
359      */

360     public DatabaseEntry getKey()
361         throws DatabaseException {
362
363         return copyEntry(key);
364     }
365
366     /**
367      * Javadoc for this public method is generated via
368      * the doc templates in the doc_src directory.
369      */

370     public SequenceStats getStats(StatsConfig config)
371         throws DatabaseException {
372
373         if (config == null) {
374             config = StatsConfig.DEFAULT;
375         }
376
377         if (!config.getFast()) {
378
379             /*
380              * storedValue may have been updated by another handle since it
381              * was last read by this handle. Fetch the last written value.
382              * READ_UNCOMMITTED must be used to avoid lock conflicts.
383              */

384             Cursor cursor = db.openCursor(null, null);
385             try {
386                 readDataRequired(cursor, LockMode.READ_UNCOMMITTED);
387             } finally {
388                 cursor.close();
389             }
390         }
391
392         SequenceStats stats = new SequenceStats
393             (nGets,
394              nCachedGets,
395              storedValue,
396              cacheValue,
397              cacheLast,
398              rangeMin,
399              rangeMax,
400              cacheSize);
401
402         if (config.getClear()) {
403             nGets = 0;
404             nCachedGets = 0;
405         }
406
407         return stats;
408     }
409
410     /**
411      * Reads persistent fields from the sequence record.
412      * Throws an exception if the key is not present in the database.
413      */

414     private void readDataRequired(Cursor cursor, LockMode lockMode)
415         throws DatabaseException {
416
417         if (!readData(cursor, lockMode)) {
418             throw new DatabaseException
419                 ("The sequence record has been deleted while it is open.");
420         }
421     }
422
423     /**
424      * Reads persistent fields from the sequence record.
425      * Returns false if the key is not present in the database.
426      */

427     private boolean readData(Cursor cursor, LockMode lockMode)
428         throws DatabaseException {
429
430         /* Fetch the sequence record. */
431         DatabaseEntry data = new DatabaseEntry();
432         OperationStatus status = cursor.getSearchKey(key, data, lockMode);
433         if (status != OperationStatus.SUCCESS) {
434             return false;
435         }
436         ByteBuffer JavaDoc buf = ByteBuffer.wrap(data.getData());
437
438         /* Get the persistent fields from the record data. */
439         byte ignoreVersionForNow = buf.get();
440         byte flags = buf.get();
441         rangeMin = LogUtils.readLong(buf);
442         rangeMax = LogUtils.readLong(buf);
443         storedValue = LogUtils.readLong(buf);
444
445         increment = (flags & FLAG_INCR) != 0;
446         wrapAllowed = (flags & FLAG_WRAP) != 0;
447         overflow = (flags & FLAG_OVER) != 0;
448
449         return true;
450     }
451
452     /**
453      * Makes a storable database entry from the persistent fields.
454      */

455     private DatabaseEntry makeData() {
456
457         byte[] data = new byte[MAX_DATA_SIZE];
458         ByteBuffer JavaDoc buf = ByteBuffer.wrap(data);
459
460         byte flags = 0;
461         if (increment) {
462             flags |= FLAG_INCR;
463         }
464         if (wrapAllowed) {
465             flags |= FLAG_WRAP;
466         }
467         if (overflow) {
468             flags |= FLAG_OVER;
469         }
470
471         buf.put(CURRENT_VERSION);
472         buf.put(flags);
473         LogUtils.writeLong(buf, rangeMin);
474         LogUtils.writeLong(buf, rangeMax);
475         LogUtils.writeLong(buf, storedValue);
476
477         return new DatabaseEntry(data, 0, buf.position());
478     }
479
480     /**
481      * Returns a deep copy of the given database entry.
482      */

483     private DatabaseEntry copyEntry(DatabaseEntry entry) {
484
485     int len = entry.getSize();
486         byte[] data;
487     if (len == 0) {
488         data = LogUtils.ZERO_LENGTH_BYTE_ARRAY;
489     } else {
490         data = new byte[len];
491         System.arraycopy
492         (entry.getData(), entry.getOffset(), data, 0, data.length);
493     }
494
495         return new DatabaseEntry(data);
496     }
497 }
498
Popular Tags