KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > persist > PrimaryIndex


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: PrimaryIndex.java,v 1.17 2006/12/04 02:49:52 mark Exp $
7  */

8
9 package com.sleepycat.persist;
10
11 import java.util.Map JavaDoc;
12 import java.util.SortedMap JavaDoc;
13
14 import com.sleepycat.bind.EntityBinding;
15 import com.sleepycat.bind.EntryBinding;
16 import com.sleepycat.collections.StoredSortedMap;
17 import com.sleepycat.je.Cursor;
18 import com.sleepycat.je.Database;
19 import com.sleepycat.je.DatabaseEntry;
20 import com.sleepycat.je.DatabaseException;
21 import com.sleepycat.je.Environment;
22 import com.sleepycat.je.LockMode;
23 import com.sleepycat.je.OperationStatus;
24 import com.sleepycat.je.Transaction;
25 import com.sleepycat.persist.impl.PersistEntityBinding;
26 import com.sleepycat.persist.impl.PersistKeyAssigner;
27 import com.sleepycat.persist.model.Entity;
28 import com.sleepycat.persist.model.PrimaryKey;
29
30 /**
31  * The primary index for an entity class and its primary key.
32  *
33  * <p>{@code PrimaryIndex} objects are thread-safe. Multiple threads may
34  * safely call the methods of a shared {@code PrimaryIndex} object.</p>
35  *
36  * <p>{@code PrimaryIndex} implements {@link EntityIndex} to map the primary
37  * key type (PK) to the entity type (E).</p>
38  *
39  * <p>The {@link Entity} annotation may be used to define an entity class and
40  * the {@link PrimaryKey} annotation may be used to define a primary key as
41  * shown in the following example.</p>
42  *
43  * <pre class="code">
44  * {@literal @Entity}
45  * class Employee {
46  *
47  * {@literal @PrimaryKey}
48  * long id;
49  *
50  * String name;
51  *
52  * Employee(long id, String name) {
53  * this.id = id;
54  * this.name = name;
55  * }
56  *
57  * private Employee() {} // For bindings
58  * }</pre>
59  *
60  * <p>To obtain the {@code PrimaryIndex} for a given entity class, call {@link
61  * EntityStore#getPrimaryIndex EntityStore.getPrimaryIndex}, passing the
62  * primary key class and the entity class. For example:</p>
63  *
64  * <pre class="code">
65  * EntityStore store = new EntityStore(...);
66  *
67  * {@code PrimaryIndex<Long,Employee>} primaryIndex =
68  * store.getPrimaryIndex(Long.class, Employee.class);</pre>
69  * </pre>
70  *
71  * <p>Note that {@code Long.class} is passed as the primary key class, but the
72  * primary key field has the primitive type {@code long}. When a primitive
73  * primary key field is used, the corresponding primitive wrapper class is used
74  * to access the primary index. For more information on key field types, see
75  * {@link PrimaryKey}.</p>
76  *
77  * <p>The {@code PrimaryIndex} provides the primary storage and access methods
78  * for the instances of a particular entity class. Entities are inserted and
79  * updated in the {@code PrimaryIndex} by calling a method in the family of
80  * {@link #put} methods. The {@link #put} method will insert the entity if no
81  * entity with the same primary key already exists. If an entity with the same
82  * primary key does exist, it will update the entity and return the existing
83  * (old) entity. For example:</p>
84  *
85  * <pre class="code">
86  * Employee oldEntity;
87  * oldEntity = primaryIndex.put(new Employee(1, "Jane Smith")); // Inserts an entity
88  * assert oldEntity == null;
89  * oldEntity = primaryIndex.put(new Employee(2, "Joan Smith")); // Inserts an entity
90  * assert oldEntity == null;
91  * oldEntity = primaryIndex.put(new Employee(2, "Joan M. Smith")); // Updates an entity
92  * assert oldEntity != null;</pre>
93  *
94  * <p>The {@link #putNoReturn} method can be used to avoid the overhead of
95  * returning the existing entity, when the existing entity is not important to
96  * the application. The return type of {@link #putNoReturn} is void. For
97  * example:</p>
98  *
99  * <pre class="code">
100  * primaryIndex.putNoReturn(new Employee(1, "Jane Smith")); // Inserts an entity
101  * primaryIndex.putNoReturn(new Employee(2, "Joan Smith")); // Inserts an entity
102  * primaryIndex.putNoReturn(new Employee(2, "Joan M. Smith")); // Updates an entity</pre>
103  *
104  * <p>The {@link #putNoOverwrite} method can be used to ensure that an existing
105  * entity is not overwritten. {@link #putNoOverwrite} returns true if the
106  * entity was inserted, or false if an existing entity exists and no action was
107  * taken. For example:<p>
108  *
109  * <pre class="code">
110  * boolean inserted;
111  * inserted = primaryIndex.putNoOverwrite(new Employee(1, "Jane Smith")); // Inserts an entity
112  * assert inserted;
113  * inserted = primaryIndex.putNoOverwrite(new Employee(2, "Joan Smith")); // Inserts an entity
114  * assert inserted;
115  * inserted = primaryIndex.putNoOverwrite(new Employee(2, "Joan M. Smith")); // <strong>No action was taken!</strong>
116  * assert !inserted;</pre>
117  *
118  * <p>Primary key values must be unique, in other words, each instance of a
119  * given entity class must have a distinct primary key value. Rather than
120  * assigning the unique primary key values yourself, a <em>sequence</em> can be
121  * used to assign sequential integer values automatically, starting with the
122  * value 1 (one). A sequence is defined using the {@link PrimaryKey#sequence}
123  * annotation property. For example:</p>
124  *
125  * <pre class="code">
126  * {@literal @Entity}
127  * class Employee {
128  *
129  * {@literal @PrimaryKey(sequence="ID")}
130  * long id;
131  *
132  * String name;
133  *
134  * Employee(String name) {
135  * this.name = name;
136  * }
137  *
138  * private Employee() {} // For bindings
139  * }</pre>
140  *
141  * <p>The name of the sequence used above is "ID". Any name can be used. If
142  * the same sequence name is used in more than one entity class, the sequence
143  * will be shared by those classes, in other words, a single sequence of
144  * integers will be used for all instances of those classes. See {@link
145  * PrimaryKey#sequence} for more information.</p>
146  *
147  * <p>Any method in the family of {@link #put} methods may be used to insert
148  * entities where the primary key is assigned from a sequence. When the {@link
149  * #put} method returns, the primary key field of the entity object will be set
150  * to the assigned key value. For example:</p>
151  *
152  * <pre class="code">
153  * Employee employee;
154  * employee = new Employee("Jane Smith");
155  * primaryIndex.putNoReturn(employee); // Inserts an entity
156  * assert employee.id == 1;
157  * employee = new Employee("Joan Smith");
158  * primaryIndex.putNoReturn(employee); // Inserts an entity
159  * assert employee.id == 2;</pre>
160  *
161  * <p>This begs the question: How do you update an existing entity, without
162  * assigning a new primary key? The answer is that the {@link #put} methods
163  * will only assign a new key from the sequence if the primary key field is
164  * zero or null (for reference types). If an entity with a non-zero and
165  * non-null key field is passed to a {@link #put} method, any existing entity
166  * with that primary key value will be updated. For example:</p>
167  *
168  * <pre class="code">
169  * Employee employee;
170  * employee = new Employee("Jane Smith");
171  * primaryIndex.putNoReturn(employee); // Inserts an entity
172  * assert employee.id == 1;
173  * employee = new Employee("Joan Smith");
174  * primaryIndex.putNoReturn(employee); // Inserts an entity
175  * assert employee.id == 2;
176  * employee.name = "Joan M. Smith";
177  * primaryIndex.putNoReturn(employee); // Updates an existing entity
178  * assert employee.id == 2;</pre>
179  *
180  * <p>Since {@code PrimaryIndex} implements the {@link EntityIndex} interface,
181  * it shares the common index methods for retrieving and deleting entities,
182  * opening cursors and using transactions. See {@link EntityIndex} for more
183  * information on these topics.</p>
184  *
185  * <p>Note that when using an index, keys and values are stored and retrieved
186  * by value not by reference. In other words, if an entity object is stored
187  * and then retrieved, or retrieved twice, each object will be a separate
188  * instance. For example, in the code below the assertion will always
189  * fail.</p>
190  * <pre class="code">
191  * MyKey key = ...;
192  * MyEntity entity1 = new MyEntity(key, ...);
193  * index.put(entity1);
194  * MyEntity entity2 = index.get(key);
195  * assert entity1 == entity2; // always fails!
196  * </pre>
197  *
198  * @author Mark Hayes
199  */

200 public class PrimaryIndex<PK,E> extends BasicIndex<PK,E> {
201
202     private Class JavaDoc<E> entityClass;
203     private EntityBinding entityBinding;
204     private SortedMap JavaDoc<PK,E> map;
205     private PersistKeyAssigner keyAssigner;
206
207     /**
208      * Creates a primary index without using an <code>EntityStore</code>.
209      *
210      * <p>This constructor is not normally needed and is provided for
211      * applications that wish to use custom bindings along with the Direct
212      * Persistence Layer. Normally, {@link EntityStore#getPrimaryIndex
213      * getPrimaryIndex} is used instead.</p>
214      *
215      * <p>Note that when this constructor is used directly, primary keys cannot
216      * be automatically assigned from a sequence. The key assignment feature
217      * requires knowledge of the primary key field, which is only available if
218      * an <code>EntityStore</code> is used. Of course, primary keys may be
219      * assigned from a sequence manually before calling the <code>put</code>
220      * methods in this class.</p>
221      *
222      * @param database the primary database.
223      *
224      * @param keyClass the class of the primary key.
225      *
226      * @param keyBinding the binding to be used for primary keys.
227      *
228      * @param entityClass the class of the entities stored in this index.
229      *
230      * @param entityBinding the binding to be used for entities.
231      */

232     public PrimaryIndex(Database database,
233                         Class JavaDoc<PK> keyClass,
234                         EntryBinding keyBinding,
235                         Class JavaDoc<E> entityClass,
236                         EntityBinding entityBinding)
237         throws DatabaseException {
238
239         super(database, keyClass, keyBinding,
240               new EntityValueAdapter(entityClass, entityBinding, false));
241
242         this.entityClass = entityClass;
243         this.entityBinding = entityBinding;
244
245         if (entityBinding instanceof PersistEntityBinding) {
246             keyAssigner =
247                 ((PersistEntityBinding) entityBinding).getKeyAssigner();
248         }
249     }
250
251     /**
252      * Returns the underlying database for this index.
253      *
254      * @return the database.
255      */

256     public Database getDatabase() {
257         return db;
258     }
259
260     /**
261      * Returns the primary key class for this index.
262      *
263      * @return the key class.
264      */

265     public Class JavaDoc<PK> getKeyClass() {
266         return keyClass;
267     }
268
269     /**
270      * Returns the primary key binding for this index.
271      *
272      * @return the key binding.
273      */

274     public EntryBinding getKeyBinding() {
275         return keyBinding;
276     }
277
278     /**
279      * Returns the entity class for this index.
280      *
281      * @return the entity class.
282      */

283     public Class JavaDoc<E> getEntityClass() {
284         return entityClass;
285     }
286
287     /**
288      * Returns the entity binding for this index.
289      *
290      * @return the entity binding.
291      */

292     public EntityBinding getEntityBinding() {
293         return entityBinding;
294     }
295
296     /**
297      * Inserts an entity and returns null, or updates it if the primary key
298      * already exists and returns the existing entity.
299      *
300      * <p>If a {@link PrimaryKey#sequence} is used and the primary key field of
301      * the given entity is null or zero, this method will assign the next value
302      * from the sequence to the primary key field of the given entity.</p>
303      *
304      * <p>Auto-commit is used implicitly if the store is transactional.</p>
305      *
306      * @param entity the entity to be inserted or updated.
307      *
308      * @return the existing entity that was updated, or null if the entity was
309      * inserted.
310      */

311     public E put(E entity)
312         throws DatabaseException {
313
314         return put(null, entity);
315     }
316
317     /**
318      * Inserts an entity and returns null, or updates it if the primary key
319      * already exists and returns the existing entity.
320      *
321      * <p>If a {@link PrimaryKey#sequence} is used and the primary key field of
322      * the given entity is null or zero, this method will assign the next value
323      * from the sequence to the primary key field of the given entity.</p>
324      *
325      * @param txn the transaction used to protect this operation, null to use
326      * auto-commit, or null if the store is non-transactional.
327      *
328      * @param entity the entity to be inserted or updated.
329      *
330      * @return the existing entity that was updated, or null if the entity was
331      * inserted.
332      */

333     public E put(Transaction txn, E entity)
334         throws DatabaseException {
335
336         DatabaseEntry keyEntry = new DatabaseEntry();
337         DatabaseEntry dataEntry = new DatabaseEntry();
338         assignKey(entity, keyEntry);
339
340         boolean autoCommit = false;
341     Environment env = db.getEnvironment();
342         if (transactional &&
343         txn == null &&
344         env.getThreadTransaction() == null) {
345             txn = env.beginTransaction(null, null);
346             autoCommit = true;
347         }
348
349         boolean failed = true;
350         Cursor cursor = db.openCursor(txn, null);
351         try {
352             while (true) {
353                 OperationStatus status =
354                     cursor.getSearchKey(keyEntry, dataEntry, LockMode.RMW);
355                 if (status == OperationStatus.SUCCESS) {
356                     E existing =
357                         (E) entityBinding.entryToObject(keyEntry, dataEntry);
358                     entityBinding.objectToData(entity, dataEntry);
359                     cursor.put(keyEntry, dataEntry);
360                     failed = false;
361                     return existing;
362                 } else {
363                     entityBinding.objectToData(entity, dataEntry);
364                     status = cursor.putNoOverwrite(keyEntry, dataEntry);
365                     if (status != OperationStatus.KEYEXIST) {
366                         failed = false;
367                         return null;
368                     }
369                 }
370             }
371         } finally {
372             cursor.close();
373             if (autoCommit) {
374                 if (failed) {
375                     txn.abort();
376                 } else {
377                     txn.commit();
378                 }
379             }
380         }
381     }
382
383     /**
384      * Inserts an entity, or updates it if the primary key already exists (does
385      * not return the existing entity). This method may be used instead of
386      * {@link #put(Object)} to save the overhead of returning the existing
387      * entity.
388      *
389      * <p>If a {@link PrimaryKey#sequence} is used and the primary key field of
390      * the given entity is null or zero, this method will assign the next value
391      * from the sequence to the primary key field of the given entity.</p>
392      *
393      * <p>Auto-commit is used implicitly if the store is transactional.</p>
394      *
395      * @param entity the entity to be inserted or updated.
396      */

397     public void putNoReturn(E entity)
398         throws DatabaseException {
399
400         putNoReturn(null, entity);
401     }
402
403     /**
404      * Inserts an entity, or updates it if the primary key already exists (does
405      * not return the existing entity). This method may be used instead of
406      * {@link #put(Transaction,Object)} to save the overhead of returning the
407      * existing entity.
408      *
409      * <p>If a {@link PrimaryKey#sequence} is used and the primary key field of
410      * the given entity is null or zero, this method will assign the next value
411      * from the sequence to the primary key field of the given entity.</p>
412      *
413      * @param txn the transaction used to protect this operation, null to use
414      * auto-commit, or null if the store is non-transactional.
415      *
416      * @param entity the entity to be inserted or updated.
417      */

418     public void putNoReturn(Transaction txn, E entity)
419         throws DatabaseException {
420
421         DatabaseEntry keyEntry = new DatabaseEntry();
422         DatabaseEntry dataEntry = new DatabaseEntry();
423         assignKey(entity, keyEntry);
424         entityBinding.objectToData(entity, dataEntry);
425
426         db.put(txn, keyEntry, dataEntry);
427     }
428
429     /**
430      * Inserts an entity and returns true, or returns false if the primary key
431      * already exists.
432      *
433      * <p>If a {@link PrimaryKey#sequence} is used and the primary key field of
434      * the given entity is null or zero, this method will assign the next value
435      * from the sequence to the primary key field of the given entity.</p>
436      *
437      * <p>Auto-commit is used implicitly if the store is transactional.</p>
438      *
439      * @param entity the entity to be inserted.
440      *
441      * @return true if the entity was inserted, or false if an entity with the
442      * same primary key is already present.
443      */

444     public boolean putNoOverwrite(E entity)
445         throws DatabaseException {
446
447         return putNoOverwrite(null, entity);
448     }
449
450     /**
451      * Inserts an entity and returns true, or returns false if the primary key
452      * already exists.
453      *
454      * <p>If a {@link PrimaryKey#sequence} is used and the primary key field of
455      * the given entity is null or zero, this method will assign the next value
456      * from the sequence to the primary key field of the given entity.</p>
457      *
458      * @param txn the transaction used to protect this operation, null to use
459      * auto-commit, or null if the store is non-transactional.
460      *
461      * @param entity the entity to be inserted.
462      *
463      * @return true if the entity was inserted, or false if an entity with the
464      * same primary key is already present.
465      */

466     public boolean putNoOverwrite(Transaction txn, E entity)
467         throws DatabaseException {
468
469         DatabaseEntry keyEntry = new DatabaseEntry();
470         DatabaseEntry dataEntry = new DatabaseEntry();
471         assignKey(entity, keyEntry);
472         entityBinding.objectToData(entity, dataEntry);
473
474         OperationStatus status = db.putNoOverwrite(txn, keyEntry, dataEntry);
475
476         return (status == OperationStatus.SUCCESS);
477     }
478
479     /**
480      * If we are assigning primary keys from a sequence, assign the next key
481      * and set the primary key field.
482      */

483     private void assignKey(E entity, DatabaseEntry keyEntry)
484         throws DatabaseException {
485
486         if (keyAssigner != null) {
487             if (!keyAssigner.assignPrimaryKey(entity, keyEntry)) {
488                 entityBinding.objectToKey(entity, keyEntry);
489             }
490         } else {
491             entityBinding.objectToKey(entity, keyEntry);
492         }
493     }
494
495     /*
496      * Of the EntityIndex methods only get()/map()/sortedMap() are implemented
497      * here. All other methods are implemented by BasicIndex.
498      */

499
500     public E get(PK key)
501         throws DatabaseException {
502
503         return get(null, key, null);
504     }
505
506     public E get(Transaction txn, PK key, LockMode lockMode)
507         throws DatabaseException {
508
509         DatabaseEntry keyEntry = new DatabaseEntry();
510         DatabaseEntry dataEntry = new DatabaseEntry();
511         keyBinding.objectToEntry(key, keyEntry);
512
513         OperationStatus status = db.get(txn, keyEntry, dataEntry, lockMode);
514
515         if (status == OperationStatus.SUCCESS) {
516             return (E) entityBinding.entryToObject(keyEntry, dataEntry);
517         } else {
518             return null;
519         }
520     }
521
522     public Map JavaDoc<PK,E> map() {
523         return sortedMap();
524     }
525
526     public synchronized SortedMap JavaDoc<PK,E> sortedMap() {
527         if (map == null) {
528             map = new StoredSortedMap(db, keyBinding, entityBinding, true);
529         }
530         return map;
531     }
532 }
533
Popular Tags