KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > persist > impl > Store


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: Store.java,v 1.20 2006/12/04 18:47:43 cwl Exp $
7  */

8
9 package com.sleepycat.persist.impl;
10
11 import java.util.ArrayList JavaDoc;
12 import java.util.Comparator JavaDoc;
13 import java.util.HashMap JavaDoc;
14 import java.util.HashSet JavaDoc;
15 import java.util.IdentityHashMap JavaDoc;
16 import java.util.List JavaDoc;
17 import java.util.Map JavaDoc;
18 import java.util.Set JavaDoc;
19 import java.util.WeakHashMap JavaDoc;
20
21 import com.sleepycat.bind.EntityBinding;
22 import com.sleepycat.bind.tuple.StringBinding;
23 import com.sleepycat.compat.DbCompat;
24 import com.sleepycat.je.Cursor;
25 import com.sleepycat.je.CursorConfig;
26 import com.sleepycat.je.Database;
27 import com.sleepycat.je.DatabaseConfig;
28 import com.sleepycat.je.DatabaseEntry;
29 import com.sleepycat.je.DatabaseException;
30 import com.sleepycat.je.DatabaseNotFoundException;
31 import com.sleepycat.je.Environment;
32 import com.sleepycat.je.ForeignKeyDeleteAction;
33 import com.sleepycat.je.LockMode;
34 import com.sleepycat.je.OperationStatus;
35 import com.sleepycat.je.SecondaryConfig;
36 import com.sleepycat.je.SecondaryDatabase;
37 import com.sleepycat.je.Sequence;
38 import com.sleepycat.je.SequenceConfig;
39 import com.sleepycat.je.Transaction;
40 import com.sleepycat.persist.PrimaryIndex;
41 import com.sleepycat.persist.SecondaryIndex;
42 import com.sleepycat.persist.StoreConfig;
43 import com.sleepycat.persist.evolve.Converter;
44 import com.sleepycat.persist.evolve.EvolveConfig;
45 import com.sleepycat.persist.evolve.EvolveEvent;
46 import com.sleepycat.persist.evolve.EvolveInternal;
47 import com.sleepycat.persist.evolve.EvolveListener;
48 import com.sleepycat.persist.evolve.EvolveStats;
49 import com.sleepycat.persist.evolve.Mutations;
50 import com.sleepycat.persist.model.ClassMetadata;
51 import com.sleepycat.persist.model.DeleteAction;
52 import com.sleepycat.persist.model.EntityMetadata;
53 import com.sleepycat.persist.model.EntityModel;
54 import com.sleepycat.persist.model.FieldMetadata;
55 import com.sleepycat.persist.model.ModelInternal;
56 import com.sleepycat.persist.model.PrimaryKeyMetadata;
57 import com.sleepycat.persist.model.Relationship;
58 import com.sleepycat.persist.model.SecondaryKeyMetadata;
59 import com.sleepycat.persist.raw.RawObject;
60
61 /**
62  * Base implementation for EntityStore and RawStore. The methods here
63  * correspond directly to those in EntityStore; see EntityStore documentation
64  * for details.
65  *
66  * @author Mark Hayes
67  */

68 public class Store {
69
70     private static final char NAME_SEPARATOR = '#';
71     private static final String JavaDoc NAME_PREFIX = "persist" + NAME_SEPARATOR;
72     private static final String JavaDoc DB_NAME_PREFIX = "com.sleepycat.persist.";
73     private static final String JavaDoc CATALOG_DB = DB_NAME_PREFIX + "formats";
74     private static final String JavaDoc SEQUENCE_DB = DB_NAME_PREFIX + "sequences";
75
76     private static Map JavaDoc<Environment,Map JavaDoc<String JavaDoc,PersistCatalog>> catalogPool =
77         new WeakHashMap JavaDoc<Environment,Map JavaDoc<String JavaDoc,PersistCatalog>>();
78
79     /* For unit testing. */
80     private static SyncHook syncHook;
81
82     private Environment env;
83     private boolean rawAccess;
84     private PersistCatalog catalog;
85     private EntityModel model;
86     private Mutations mutations;
87     private StoreConfig storeConfig;
88     private String JavaDoc storeName;
89     private String JavaDoc storePrefix;
90     private Map JavaDoc<String JavaDoc,PrimaryIndex> priIndexMap;
91     private Map JavaDoc<String JavaDoc,SecondaryIndex> secIndexMap;
92     private Map JavaDoc<String JavaDoc,DatabaseConfig> priConfigMap;
93     private Map JavaDoc<String JavaDoc,SecondaryConfig> secConfigMap;
94     private Map JavaDoc<String JavaDoc,PersistKeyBinding> keyBindingMap;
95     private Map JavaDoc<String JavaDoc,Sequence> sequenceMap;
96     private Map JavaDoc<String JavaDoc,SequenceConfig> sequenceConfigMap;
97     private Database sequenceDb;
98     private IdentityHashMap JavaDoc<Database,Object JavaDoc> deferredWriteDatabases;
99
100     public Store(Environment env,
101                  String JavaDoc storeName,
102                  StoreConfig config,
103                  boolean rawAccess)
104         throws DatabaseException {
105
106         this.env = env;
107         this.storeName = storeName;
108         this.rawAccess = rawAccess;
109
110         if (env == null || storeName == null) {
111             throw new NullPointerException JavaDoc
112                 ("env and storeName parameters must not be null");
113         }
114         if (config != null) {
115             model = config.getModel();
116             mutations = config.getMutations();
117         }
118         if (config == null) {
119             storeConfig = StoreConfig.DEFAULT;
120         } else {
121             storeConfig = config.cloneConfig();
122         }
123
124         storePrefix = NAME_PREFIX + storeName + NAME_SEPARATOR;
125         priIndexMap = new HashMap JavaDoc<String JavaDoc,PrimaryIndex>();
126         secIndexMap = new HashMap JavaDoc<String JavaDoc,SecondaryIndex>();
127         priConfigMap = new HashMap JavaDoc<String JavaDoc,DatabaseConfig>();
128         secConfigMap = new HashMap JavaDoc<String JavaDoc,SecondaryConfig>();
129         keyBindingMap = new HashMap JavaDoc<String JavaDoc,PersistKeyBinding>();
130         sequenceMap = new HashMap JavaDoc<String JavaDoc,Sequence>();
131         sequenceConfigMap = new HashMap JavaDoc<String JavaDoc,SequenceConfig>();
132         deferredWriteDatabases = new IdentityHashMap JavaDoc<Database,Object JavaDoc>();
133
134         if (rawAccess) {
135             /* Open a read-only catalog that uses the stored model. */
136             if (model != null) {
137                 throw new IllegalArgumentException JavaDoc
138                     ("A model may not be specified when opening a RawStore");
139             }
140             DatabaseConfig dbConfig = new DatabaseConfig();
141             dbConfig.setReadOnly(true);
142             dbConfig.setTransactional
143                 (storeConfig.getTransactional());
144             catalog = new PersistCatalog
145                 (null, env, storePrefix, storePrefix + CATALOG_DB, dbConfig,
146                  model, mutations, rawAccess, this);
147         } else {
148             /* Open the shared catalog that uses the current model. */
149             synchronized (catalogPool) {
150                 Map JavaDoc<String JavaDoc,PersistCatalog> catalogMap = catalogPool.get(env);
151                 if (catalogMap == null) {
152                     catalogMap = new HashMap JavaDoc<String JavaDoc,PersistCatalog>();
153                     catalogPool.put(env, catalogMap);
154                 }
155                 catalog = catalogMap.get(storeName);
156                 if (catalog != null) {
157                     catalog.openExisting();
158                 } else {
159                     Transaction txn = null;
160                     if (storeConfig.getTransactional() &&
161             env.getThreadTransaction() == null) {
162                         txn = env.beginTransaction(null, null);
163                     }
164                     boolean success = false;
165                     try {
166                         DatabaseConfig dbConfig = new DatabaseConfig();
167                         dbConfig.setAllowCreate(storeConfig.getAllowCreate());
168                         dbConfig.setReadOnly(storeConfig.getReadOnly());
169                         dbConfig.setTransactional
170                             (storeConfig.getTransactional());
171                         catalog = new PersistCatalog
172                             (txn, env, storePrefix, storePrefix + CATALOG_DB,
173                              dbConfig, model, mutations, rawAccess, this);
174                         catalogMap.put(storeName, catalog);
175                         success = true;
176                     } finally {
177                         if (txn != null) {
178                             if (success) {
179                                 txn.commit();
180                             } else {
181                                 txn.abort();
182                             }
183                         }
184                     }
185                 }
186             }
187         }
188
189         /* Get the merged mutations from the catalog. */
190         mutations = catalog.getMutations();
191
192         /*
193          * If there is no model parameter, use the default or stored model
194          * obtained from the catalog.
195          */

196         model = catalog.getResolvedModel();
197
198         /*
199          * Give the model a reference to the catalog to fully initialize the
200          * model. Only then may we initialize the Converter mutations, which
201          * themselves may call model methods and expect the model to be fully
202          * initialized.
203          */

204         ModelInternal.setCatalog(model, catalog);
205         for (Converter converter : mutations.getConverters()) {
206             converter.getConversion().initialize(model);
207         }
208     }
209
210     public Environment getEnvironment() {
211         return env;
212     }
213
214     public StoreConfig getConfig() {
215         return storeConfig.cloneConfig();
216     }
217
218     public String JavaDoc getStoreName() {
219         return storeName;
220     }
221
222     public void dumpCatalog() {
223         catalog.dump();
224     }
225
226     public static Set JavaDoc<String JavaDoc> getStoreNames(Environment env)
227         throws DatabaseException {
228
229         Set JavaDoc<String JavaDoc> set = new HashSet JavaDoc<String JavaDoc>();
230         for (Object JavaDoc o : env.getDatabaseNames()) {
231             String JavaDoc s = (String JavaDoc) o;
232             if (s.startsWith(NAME_PREFIX)) {
233                 int start = NAME_PREFIX.length();
234                 int end = s.indexOf(NAME_SEPARATOR, start);
235                 set.add(s.substring(start, end));
236             }
237         }
238         return set;
239     }
240
241     public EntityModel getModel() {
242         return model;
243     }
244
245     public Mutations getMutations() {
246         return mutations;
247     }
248
249     /**
250      * A getPrimaryIndex with extra parameters for opening a raw store.
251      * primaryKeyClass and entityClass are used for generic typing; for a raw
252      * store, these should always be Object.class and RawObject.class.
253      * primaryKeyClassName is used for consistency checking and should be null
254      * for a raw store only. entityClassName is used to identify the store and
255      * may not be null.
256      */

257     public synchronized <PK,E> PrimaryIndex<PK,E>
258         getPrimaryIndex(Class JavaDoc<PK> primaryKeyClass,
259                         String JavaDoc primaryKeyClassName,
260                         Class JavaDoc<E> entityClass,
261                         String JavaDoc entityClassName)
262         throws DatabaseException {
263
264         assert (rawAccess && entityClass == RawObject.class) ||
265               (!rawAccess && entityClass != RawObject.class);
266         assert (rawAccess && primaryKeyClassName == null) ||
267               (!rawAccess && primaryKeyClassName != null);
268
269         checkOpen();
270
271         PrimaryIndex<PK,E> priIndex = priIndexMap.get(entityClassName);
272         if (priIndex == null) {
273
274             /* Check metadata. */
275             EntityMetadata entityMeta = checkEntityClass(entityClassName);
276             PrimaryKeyMetadata priKeyMeta = entityMeta.getPrimaryKey();
277             if (primaryKeyClassName == null) {
278                 primaryKeyClassName = priKeyMeta.getClassName();
279             } else {
280                 String JavaDoc expectClsName =
281                     SimpleCatalog.keyClassName(priKeyMeta.getClassName());
282                 if (!primaryKeyClassName.equals(expectClsName)) {
283                     throw new IllegalArgumentException JavaDoc
284                         ("Wrong primary key class: " + primaryKeyClassName +
285                          " Correct class is: " + expectClsName);
286                 }
287             }
288
289             /* Create bindings. */
290             PersistEntityBinding entityBinding =
291                 new PersistEntityBinding(catalog, entityClassName, rawAccess);
292             PersistKeyBinding keyBinding = getKeyBinding(primaryKeyClassName);
293
294             /* If not read-only, get the primary key sequence. */
295             String JavaDoc seqName = priKeyMeta.getSequenceName();
296             if (!storeConfig.getReadOnly() && seqName != null) {
297                 entityBinding.keyAssigner = new PersistKeyAssigner
298                     (keyBinding, entityBinding, getSequence(seqName));
299             }
300
301             /* Use a single transaction for all opens. */
302             Transaction txn = null;
303             DatabaseConfig dbConfig = getPrimaryConfig(entityMeta);
304             if (dbConfig.getTransactional() &&
305         env.getThreadTransaction() == null) {
306                 txn = env.beginTransaction(null, null);
307             }
308             boolean success = false;
309             try {
310                 /* Open the primary database. */
311                 String JavaDoc dbName = storePrefix + entityClassName;
312                 Database db = env.openDatabase(txn, dbName, dbConfig);
313
314                 /* Create index object. */
315                 priIndex = new PrimaryIndex
316                     (db, primaryKeyClass, keyBinding, entityClass,
317                      entityBinding);
318
319                 /* Update index and database maps. */
320                 priIndexMap.put(entityClassName, priIndex);
321                 if (DbCompat.getDeferredWrite(dbConfig)) {
322                     deferredWriteDatabases.put(db, null);
323                 }
324
325                 /* If not read-only, open all associated secondaries. */
326                 if (!dbConfig.getReadOnly()) {
327                     openSecondaryIndexes(entityMeta);
328                 }
329                 success = true;
330             } finally {
331                 if (txn != null) {
332                     if (success) {
333                         txn.commit();
334                     } else {
335                         txn.abort();
336                     }
337                 }
338             }
339         }
340         return priIndex;
341     }
342
343     /**
344      * A getSecondaryIndex with extra parameters for opening a raw store.
345      * keyClassName is used for consistency checking and should be null for a
346      * raw store only.
347      */

348     public synchronized <SK,PK,E1,E2 extends E1> SecondaryIndex<SK,PK,E2>
349         getSecondaryIndex(PrimaryIndex<PK,E1> primaryIndex,
350                           Class JavaDoc<E2> entityClass,
351                           String JavaDoc entityClassName,
352                           Class JavaDoc<SK> keyClass,
353                           String JavaDoc keyClassName,
354                           String JavaDoc keyName)
355         throws DatabaseException {
356
357         assert (rawAccess && keyClassName == null) ||
358               (!rawAccess && keyClassName != null);
359
360         checkOpen();
361
362         EntityMetadata entityMeta = null;
363         SecondaryKeyMetadata secKeyMeta = null;
364
365         /* Validate the subclass for a subclass index. */
366         if (entityClass != primaryIndex.getEntityClass()) {
367             entityMeta = model.getEntityMetadata(entityClassName);
368             assert entityMeta != null;
369             secKeyMeta = checkSecKey(entityMeta, keyName);
370             String JavaDoc subclassName = entityClass.getName();
371             String JavaDoc declaringClassName = secKeyMeta.getDeclaringClassName();
372             if (!subclassName.equals(declaringClassName)) {
373                 throw new IllegalArgumentException JavaDoc
374                     ("Key for subclass " + subclassName +
375                      " is declared in a different class: " +
376                      makeSecName(declaringClassName, keyName));
377             }
378         }
379
380         /*
381          * Even though the primary is already open, we can't assume the
382          * secondary is open because we don't automatically open all
383          * secondaries when the primary is read-only. Use auto-commit (a null
384          * transaction) since we're opening only one database.
385          */

386         String JavaDoc secName = makeSecName(entityClassName, keyName);
387         SecondaryIndex<SK,PK,E2> secIndex = secIndexMap.get(secName);
388         if (secIndex == null) {
389             if (entityMeta == null) {
390                 entityMeta = model.getEntityMetadata(entityClassName);
391                 assert entityMeta != null;
392             }
393             if (secKeyMeta == null) {
394                 secKeyMeta = checkSecKey(entityMeta, keyName);
395             }
396
397             /* Check metadata. */
398             if (keyClassName == null) {
399                 keyClassName = getSecKeyClass(secKeyMeta);
400             } else {
401                 String JavaDoc expectClsName = getSecKeyClass(secKeyMeta);
402                 if (!keyClassName.equals(expectClsName)) {
403                     throw new IllegalArgumentException JavaDoc
404                         ("Wrong secondary key class: " + keyClassName +
405                          " Correct class is: " + expectClsName);
406                 }
407             }
408
409             secIndex = openSecondaryIndex
410                 (null, primaryIndex, entityClass, entityMeta,
411                  keyClass, keyClassName, secKeyMeta, secName);
412         }
413         return secIndex;
414     }
415
416     /**
417      * Opens any secondary indexes defined in the given entity metadata that
418      * are not already open. This method is called when a new entity subclass
419      * is encountered when an instance of that class is stored, and the
420      * EntityStore.getSubclassIndex has not been previously called for that
421      * class. [#15247]
422      */

423     synchronized void openSecondaryIndexes(EntityMetadata entityMeta)
424         throws DatabaseException {
425
426         String JavaDoc entityClassName = entityMeta.getClassName();
427         PrimaryIndex<Object JavaDoc,Object JavaDoc> priIndex =
428             priIndexMap.get(entityClassName);
429         assert priIndex != null;
430         Class JavaDoc<Object JavaDoc> entityClass = priIndex.getEntityClass();
431
432         for (SecondaryKeyMetadata secKeyMeta :
433              entityMeta.getSecondaryKeys().values()) {
434             String JavaDoc keyName = secKeyMeta.getKeyName();
435             String JavaDoc secName = makeSecName(entityClassName, keyName);
436             SecondaryIndex<Object JavaDoc,Object JavaDoc,Object JavaDoc> secIndex =
437                 secIndexMap.get(secName);
438             if (secIndex == null) {
439                 String JavaDoc keyClassName = getSecKeyClass(secKeyMeta);
440                 /* XXX: should not require class in raw mode. */
441                 Class JavaDoc keyClass =
442                     SimpleCatalog.keyClassForName(keyClassName);
443                 openSecondaryIndex
444                     (null, priIndex, entityClass, entityMeta,
445                      keyClass, keyClassName, secKeyMeta,
446                      makeSecName
447                         (entityClassName, secKeyMeta.getKeyName()));
448             }
449         }
450     }
451
452     /**
453      * Opens a secondary index with a given transaction and adds it to the
454      * secIndexMap. We assume that the index is not already open.
455      */

456     private <SK,PK,E1,E2 extends E1> SecondaryIndex<SK,PK,E2>
457         openSecondaryIndex(Transaction txn,
458                            PrimaryIndex<PK,E1> primaryIndex,
459                            Class JavaDoc<E2> entityClass,
460                            EntityMetadata entityMeta,
461                            Class JavaDoc<SK> keyClass,
462                            String JavaDoc keyClassName,
463                            SecondaryKeyMetadata secKeyMeta,
464                            String JavaDoc secName)
465         throws DatabaseException {
466
467         assert !secIndexMap.containsKey(secName);
468         String JavaDoc dbName = storePrefix + secName;
469         SecondaryConfig config =
470             getSecondaryConfig(secName, entityMeta, keyClassName, secKeyMeta);
471         Database priDb = primaryIndex.getDatabase();
472         DatabaseConfig priConfig = priDb.getConfig();
473
474         String JavaDoc relatedClsName = secKeyMeta.getRelatedEntity();
475         if (relatedClsName != null) {
476             PrimaryIndex relatedIndex = priIndexMap.get(relatedClsName);
477             if (relatedIndex == null) {
478                 EntityMetadata relatedEntityMeta =
479                     checkEntityClass(relatedClsName);
480                 Class JavaDoc relatedKeyCls;
481                 String JavaDoc relatedKeyClsName;
482                 Class JavaDoc relatedCls;
483                 if (rawAccess) {
484                     relatedCls = RawObject.class;
485                     relatedKeyCls = Object JavaDoc.class;
486                     relatedKeyClsName = null;
487                 } else {
488                     try {
489                         relatedCls = Class.forName(relatedClsName);
490                     } catch (ClassNotFoundException JavaDoc e) {
491                         throw new IllegalArgumentException JavaDoc
492                             ("Foreign key database class not found: " +
493                              relatedClsName);
494                     }
495                     relatedKeyClsName = SimpleCatalog.keyClassName
496                         (relatedEntityMeta.getPrimaryKey().getClassName());
497                     relatedKeyCls =
498                         SimpleCatalog.keyClassForName(relatedKeyClsName);
499                 }
500                 /* XXX Check to make sure cycles are not possible here. */
501                 relatedIndex = getPrimaryIndex
502                     (relatedKeyCls, relatedKeyClsName,
503                      relatedCls, relatedClsName);
504             }
505             config.setForeignKeyDatabase(relatedIndex.getDatabase());
506         }
507
508         if (config.getTransactional() != priConfig.getTransactional() ||
509             DbCompat.getDeferredWrite(config) !=
510             DbCompat.getDeferredWrite(priConfig) ||
511             config.getReadOnly() != priConfig.getReadOnly()) {
512             throw new IllegalArgumentException JavaDoc
513                 ("One of these properties was changed to be inconsistent" +
514                  " with the associated primary database: " +
515                  " Transactional, DeferredWrite, ReadOnly");
516         }
517
518         PersistKeyBinding keyBinding = getKeyBinding(keyClassName);
519         
520         SecondaryDatabase db =
521             env.openSecondaryDatabase(txn, dbName, priDb, config);
522         SecondaryIndex<SK,PK,E2> secIndex = new SecondaryIndex
523             (db, null, primaryIndex, keyClass, keyBinding);
524
525         /* Update index and database maps. */
526         secIndexMap.put(secName, secIndex);
527         if (DbCompat.getDeferredWrite(config)) {
528             deferredWriteDatabases.put(db, null);
529         }
530         return secIndex;
531     }
532
533     public void sync()
534         throws DatabaseException {
535         
536         List JavaDoc<Database> dbs = new ArrayList JavaDoc<Database>();
537         synchronized (this) {
538             dbs.addAll(deferredWriteDatabases.keySet());
539         }
540         int nDbs = dbs.size();
541         if (nDbs > 0) {
542             for (int i = 0; i < nDbs; i += 1) {
543                 Database db = dbs.get(i);
544                 boolean flushLog = (i == nDbs - 1);
545                 DbCompat.syncDeferredWrite(db, flushLog);
546                 /* Call hook for unit testing. */
547                 if (syncHook != null) {
548                     syncHook.onSync(db, flushLog);
549                 }
550             }
551         }
552     }
553
554     public void truncateClass(Class JavaDoc entityClass)
555         throws DatabaseException {
556         
557         truncateClass(null, entityClass);
558     }
559
560     public synchronized void truncateClass(Transaction txn, Class JavaDoc entityClass)
561         throws DatabaseException {
562
563         checkOpen();
564
565         /* Close primary and secondary databases. */
566         closeClass(entityClass);
567
568         String JavaDoc clsName = entityClass.getName();
569         EntityMetadata entityMeta = checkEntityClass(clsName);
570
571         /*
572          * Truncate the primary first and let any exceptions propogate
573          * upwards. Then truncate each secondary, only throwing the first
574          * exception.
575          */

576         String JavaDoc dbName = storePrefix + clsName;
577         boolean primaryExists = true;
578         try {
579             env.truncateDatabase(txn, dbName, false);
580         } catch (DatabaseNotFoundException ignored) {
581             primaryExists = false;
582         }
583         if (primaryExists) {
584             DatabaseException firstException = null;
585             for (SecondaryKeyMetadata keyMeta :
586                  entityMeta.getSecondaryKeys().values()) {
587                 try {
588                     env.truncateDatabase
589                         (txn,
590                          storePrefix +
591                          makeSecName(clsName, keyMeta.getKeyName()),
592                          false);
593                 } catch (DatabaseNotFoundException ignored) {
594                     /* Ignore secondaries that do not exist. */
595                 } catch (DatabaseException e) {
596                     if (firstException == null) {
597                         firstException = e;
598                     }
599                 }
600             }
601             if (firstException != null) {
602                 throw firstException;
603             }
604         }
605     }
606
607     public synchronized void closeClass(Class JavaDoc entityClass)
608         throws DatabaseException {
609
610         checkOpen();
611         String JavaDoc clsName = entityClass.getName();
612         EntityMetadata entityMeta = checkEntityClass(clsName);
613
614         PrimaryIndex priIndex = priIndexMap.get(clsName);
615         if (priIndex != null) {
616             /* Close the secondaries first. */
617             DatabaseException firstException = null;
618             for (SecondaryKeyMetadata keyMeta :
619                  entityMeta.getSecondaryKeys().values()) {
620
621                 String JavaDoc secName = makeSecName(clsName, keyMeta.getKeyName());
622                 SecondaryIndex secIndex = secIndexMap.get(secName);
623                 if (secIndex != null) {
624                     Database db = secIndex.getDatabase();
625                     firstException = closeDb(db, firstException);
626                     firstException =
627                         closeDb(secIndex.getKeysDatabase(), firstException);
628                     secIndexMap.remove(secName);
629                     deferredWriteDatabases.remove(db);
630                 }
631             }
632             /* Close the primary last. */
633             Database db = priIndex.getDatabase();
634             firstException = closeDb(db, firstException);
635             priIndexMap.remove(clsName);
636             deferredWriteDatabases.remove(db);
637
638             /* Throw the first exception encountered. */
639             if (firstException != null) {
640                 throw firstException;
641             }
642         }
643     }
644
645     public synchronized void close()
646         throws DatabaseException {
647
648         checkOpen();
649         DatabaseException firstException = null;
650         try {
651             if (rawAccess) {
652                 boolean allClosed = catalog.close();
653                 assert allClosed;
654             } else {
655                 synchronized (catalogPool) {
656                     Map JavaDoc<String JavaDoc,PersistCatalog> catalogMap =
657                         catalogPool.get(env);
658                     assert catalogMap != null;
659                     if (catalog.close()) {
660                         /* Remove when the reference count goes to zero. */
661                         catalogMap.remove(storeName);
662                     }
663                 }
664             }
665             catalog = null;
666         } catch (DatabaseException e) {
667             if (firstException == null) {
668                 firstException = e;
669             }
670         }
671         firstException = closeDb(sequenceDb, firstException);
672         for (SecondaryIndex index : secIndexMap.values()) {
673             firstException = closeDb(index.getDatabase(), firstException);
674             firstException = closeDb(index.getKeysDatabase(), firstException);
675         }
676         for (PrimaryIndex index : priIndexMap.values()) {
677             firstException = closeDb(index.getDatabase(), firstException);
678         }
679         if (firstException != null) {
680             throw firstException;
681         }
682     }
683
684     public synchronized Sequence getSequence(String JavaDoc name)
685         throws DatabaseException {
686
687         checkOpen();
688
689         if (storeConfig.getReadOnly()) {
690             throw new IllegalStateException JavaDoc("Store is read-only");
691         }
692         
693         Sequence seq = sequenceMap.get(name);
694         if (seq == null) {
695             if (sequenceDb == null) {
696                 String JavaDoc dbName = storePrefix + SEQUENCE_DB;
697                 DatabaseConfig dbConfig = new DatabaseConfig();
698                 dbConfig.setTransactional(storeConfig.getTransactional());
699                 dbConfig.setAllowCreate(true);
700                 sequenceDb = env.openDatabase(null, dbName, dbConfig);
701             }
702             DatabaseEntry entry = new DatabaseEntry();
703             StringBinding.stringToEntry(name, entry);
704             seq = sequenceDb.openSequence(null, entry, getSequenceConfig(name));
705             sequenceMap.put(name, seq);
706         }
707         return seq;
708     }
709
710     public synchronized SequenceConfig getSequenceConfig(String JavaDoc name) {
711         checkOpen();
712         SequenceConfig config = sequenceConfigMap.get(name);
713         if (config == null) {
714             config = new SequenceConfig();
715             config.setInitialValue(1);
716             config.setRange(1, Long.MAX_VALUE);
717             config.setCacheSize(100);
718             config.setAutoCommitNoSync(true);
719             config.setAllowCreate(!storeConfig.getReadOnly());
720             sequenceConfigMap.put(name, config);
721         }
722         return config;
723     }
724
725     public synchronized void setSequenceConfig(String JavaDoc name,
726                                                SequenceConfig config) {
727         checkOpen();
728         sequenceConfigMap.put(name, config);
729     }
730
731     public synchronized DatabaseConfig getPrimaryConfig(Class JavaDoc entityClass) {
732         checkOpen();
733         String JavaDoc clsName = entityClass.getName();
734         EntityMetadata meta = checkEntityClass(clsName);
735         return getPrimaryConfig(meta).cloneConfig();
736     }
737
738     private synchronized DatabaseConfig getPrimaryConfig(EntityMetadata meta) {
739         String JavaDoc clsName = meta.getClassName();
740         DatabaseConfig config = priConfigMap.get(clsName);
741         if (config == null) {
742             config = new DatabaseConfig();
743             config.setTransactional(storeConfig.getTransactional());
744             config.setAllowCreate(!storeConfig.getReadOnly());
745             config.setReadOnly(storeConfig.getReadOnly());
746             DbCompat.setDeferredWrite(config, storeConfig.getDeferredWrite());
747             setBtreeComparator(config, meta.getPrimaryKey().getClassName());
748             priConfigMap.put(clsName, config);
749         }
750         return config;
751     }
752
753     public synchronized void setPrimaryConfig(Class JavaDoc entityClass,
754                                               DatabaseConfig config) {
755         checkOpen();
756         String JavaDoc clsName = entityClass.getName();
757         if (priIndexMap.containsKey(clsName)) {
758             throw new IllegalStateException JavaDoc
759                 ("Cannot set config after DB is open");
760         }
761         EntityMetadata meta = checkEntityClass(clsName);
762         DatabaseConfig dbConfig = getPrimaryConfig(meta);
763         if (config.getSortedDuplicates() ||
764             config.getBtreeComparator() != dbConfig.getBtreeComparator()) {
765             throw new IllegalArgumentException JavaDoc
766                 ("One of these properties was illegally changed: " +
767                  " SortedDuplicates or BtreeComparator");
768         }
769         priConfigMap.put(clsName, config);
770     }
771
772     public synchronized SecondaryConfig getSecondaryConfig(Class JavaDoc entityClass,
773                                                            String JavaDoc keyName) {
774         checkOpen();
775         String JavaDoc entityClsName = entityClass.getName();
776         EntityMetadata entityMeta = checkEntityClass(entityClsName);
777         SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta, keyName);
778         String JavaDoc keyClassName = getSecKeyClass(secKeyMeta);
779         String JavaDoc secName = makeSecName(entityClass.getName(), keyName);
780         return (SecondaryConfig) getSecondaryConfig
781             (secName, entityMeta, keyClassName, secKeyMeta).cloneConfig();
782     }
783
784     private SecondaryConfig getSecondaryConfig(String JavaDoc secName,
785                                                EntityMetadata entityMeta,
786                                                String JavaDoc keyClassName,
787                                                SecondaryKeyMetadata
788                                                secKeyMeta) {
789         SecondaryConfig config = secConfigMap.get(secName);
790         if (config == null) {
791             /* Set common properties to match the primary DB. */
792             DatabaseConfig priConfig = getPrimaryConfig(entityMeta);
793             config = new SecondaryConfig();
794             config.setTransactional(priConfig.getTransactional());
795             config.setAllowCreate(!priConfig.getReadOnly());
796             config.setReadOnly(priConfig.getReadOnly());
797             DbCompat.setDeferredWrite
798                 (config, DbCompat.getDeferredWrite(priConfig));
799             /* Set secondary properties based on metadata. */
800             config.setAllowPopulate(true);
801             Relationship rel = secKeyMeta.getRelationship();
802             config.setSortedDuplicates(rel == Relationship.MANY_TO_ONE ||
803                                        rel == Relationship.MANY_TO_MANY);
804             setBtreeComparator(config, secKeyMeta.getClassName());
805             PersistKeyCreator keyCreator = new PersistKeyCreator
806                 (catalog, entityMeta, keyClassName, secKeyMeta);
807             if (rel == Relationship.ONE_TO_MANY ||
808                 rel == Relationship.MANY_TO_MANY) {
809                 config.setMultiKeyCreator(keyCreator);
810             } else {
811                 config.setKeyCreator(keyCreator);
812             }
813             DeleteAction deleteAction = secKeyMeta.getDeleteAction();
814             if (deleteAction != null) {
815                 ForeignKeyDeleteAction baseDeleteAction;
816                 switch (deleteAction) {
817                 case ABORT:
818                     baseDeleteAction = ForeignKeyDeleteAction.ABORT;
819                     break;
820                 case CASCADE:
821                     baseDeleteAction = ForeignKeyDeleteAction.CASCADE;
822                     break;
823                 case NULLIFY:
824                     baseDeleteAction = ForeignKeyDeleteAction.NULLIFY;
825                     break;
826                 default:
827                     throw new IllegalStateException JavaDoc(deleteAction.toString());
828                 }
829                 config.setForeignKeyDeleteAction(baseDeleteAction);
830                 if (deleteAction == DeleteAction.NULLIFY) {
831                     config.setForeignMultiKeyNullifier(keyCreator);
832                 }
833             }
834             secConfigMap.put(secName, config);
835         }
836         return config;
837     }
838
839     public synchronized void setSecondaryConfig(Class JavaDoc entityClass,
840                                                 String JavaDoc keyName,
841                                                 SecondaryConfig config) {
842         checkOpen();
843         String JavaDoc entityClsName = entityClass.getName();
844         EntityMetadata entityMeta = checkEntityClass(entityClsName);
845         SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta, keyName);
846         String JavaDoc keyClassName = getSecKeyClass(secKeyMeta);
847         String JavaDoc secName = makeSecName(entityClass.getName(), keyName);
848         if (secIndexMap.containsKey(secName)) {
849             throw new IllegalStateException JavaDoc
850                 ("Cannot set config after DB is open");
851         }
852         SecondaryConfig dbConfig =
853             getSecondaryConfig(secName, entityMeta, keyClassName, secKeyMeta);
854         if (config.getSortedDuplicates() != dbConfig.getSortedDuplicates() ||
855             config.getBtreeComparator() != dbConfig.getBtreeComparator() ||
856             config.getDuplicateComparator() != null ||
857             config.getAllowPopulate() != dbConfig.getAllowPopulate() ||
858             config.getKeyCreator() != dbConfig.getKeyCreator() ||
859             config.getMultiKeyCreator() != dbConfig.getMultiKeyCreator() ||
860             config.getForeignKeyNullifier() !=
861                 dbConfig.getForeignKeyNullifier() ||
862             config.getForeignMultiKeyNullifier() !=
863                 dbConfig.getForeignMultiKeyNullifier() ||
864             config.getForeignKeyDeleteAction() !=
865                 dbConfig.getForeignKeyDeleteAction() ||
866             config.getForeignKeyDatabase() != null) {
867             throw new IllegalArgumentException JavaDoc
868                 ("One of these properties was illegally changed: " +
869                  " SortedDuplicates, BtreeComparator, DuplicateComparator," +
870                  " AllowPopulate, KeyCreator, MultiKeyCreator," +
871                  " ForeignKeyNullifer, ForeignMultiKeyNullifier," +
872                  " ForeignKeyDeleteAction, ForeignKeyDatabase");
873         }
874         secConfigMap.put(secName, config);
875     }
876
877     private static String JavaDoc makeSecName(String JavaDoc entityClsName, String JavaDoc keyName) {
878          return entityClsName + NAME_SEPARATOR + keyName;
879     }
880
881     static String JavaDoc makePriDbName(String JavaDoc storePrefix, String JavaDoc entityClsName) {
882         return storePrefix + entityClsName;
883     }
884
885     static String JavaDoc makeSecDbName(String JavaDoc storePrefix,
886                                 String JavaDoc entityClsName,
887                                 String JavaDoc keyName) {
888         return storePrefix + makeSecName(entityClsName, keyName);
889     }
890
891     private void checkOpen() {
892         if (catalog == null) {
893             throw new IllegalStateException JavaDoc("Store has been closed");
894         }
895     }
896
897     private EntityMetadata checkEntityClass(String JavaDoc clsName) {
898         EntityMetadata meta = model.getEntityMetadata(clsName);
899         if (meta == null) {
900             throw new IllegalArgumentException JavaDoc
901                 ("Not an entity class: " + clsName);
902         }
903         return meta;
904     }
905
906     private SecondaryKeyMetadata checkSecKey(EntityMetadata entityMeta,
907                                              String JavaDoc keyName) {
908         SecondaryKeyMetadata secKeyMeta =
909             entityMeta.getSecondaryKeys().get(keyName);
910         if (secKeyMeta == null) {
911             throw new IllegalArgumentException JavaDoc
912                 ("Not a secondary key: " +
913                  makeSecName(entityMeta.getClassName(), keyName));
914         }
915         return secKeyMeta;
916     }
917
918     private String JavaDoc getSecKeyClass(SecondaryKeyMetadata secKeyMeta) {
919         String JavaDoc clsName = secKeyMeta.getElementClassName();
920         if (clsName == null) {
921             clsName = secKeyMeta.getClassName();
922         }
923         return SimpleCatalog.keyClassName(clsName);
924     }
925
926     private PersistKeyBinding getKeyBinding(String JavaDoc keyClassName) {
927         PersistKeyBinding binding = keyBindingMap.get(keyClassName);
928         if (binding == null) {
929             binding = new PersistKeyBinding(catalog, keyClassName, rawAccess);
930             keyBindingMap.put(keyClassName, binding);
931         }
932         return binding;
933     }
934
935     private void setBtreeComparator(DatabaseConfig config, String JavaDoc clsName) {
936         if (!rawAccess) {
937             ClassMetadata meta = model.getClassMetadata(clsName);
938             if (meta != null) {
939                 List JavaDoc<FieldMetadata> compositeKeyFields =
940                     meta.getCompositeKeyFields();
941                 if (compositeKeyFields != null) {
942                     Class JavaDoc keyClass = SimpleCatalog.keyClassForName(clsName);
943                     if (Comparable JavaDoc.class.isAssignableFrom(keyClass)) {
944                         Comparator JavaDoc<Object JavaDoc> cmp = new PersistComparator
945                             (clsName, compositeKeyFields,
946                              getKeyBinding(clsName));
947                         config.setBtreeComparator(cmp);
948                     }
949                 }
950             }
951         }
952     }
953
954     private DatabaseException closeDb(Database db,
955                                       DatabaseException firstException) {
956         if (db != null) {
957             try {
958                 db.close();
959             } catch (DatabaseException e) {
960                 if (firstException == null) {
961                     firstException = e;
962                 }
963             }
964         }
965         return firstException;
966     }
967
968     public EvolveStats evolve(EvolveConfig config)
969         throws DatabaseException {
970
971         checkOpen();
972         List JavaDoc<Format> toEvolve = new ArrayList JavaDoc<Format>();
973         Set JavaDoc<String JavaDoc> configToEvolve = config.getClassesToEvolve();
974         if (configToEvolve.isEmpty()) {
975             catalog.getEntityFormats(toEvolve);
976         } else {
977             for (String JavaDoc name : configToEvolve) {
978                 Format format = catalog.getFormat(name);
979                 if (format == null) {
980                     throw new IllegalArgumentException JavaDoc
981                         ("Class to evolve is not persistent: " + name);
982                 }
983                 if (!format.isEntity()) {
984                     throw new IllegalArgumentException JavaDoc
985                         ("Class to evolve is not an entity class: " + name);
986                 }
987                 toEvolve.add(format);
988             }
989         }
990
991         EvolveEvent event = EvolveInternal.newEvent();
992         for (Format format : toEvolve) {
993             if (format.getEvolveNeeded()) {
994                 evolveIndex(format, event, config.getEvolveListener());
995                 format.setEvolveNeeded(false);
996                 catalog.flush();
997             }
998         }
999
1000        return event.getStats();
1001    }
1002
1003    private void evolveIndex(Format format,
1004                             EvolveEvent event,
1005                             EvolveListener listener)
1006        throws DatabaseException {
1007
1008        Class JavaDoc entityClass = format.getType();
1009        String JavaDoc entityClassName = format.getClassName();
1010        EntityMetadata meta = model.getEntityMetadata(entityClassName);
1011        String JavaDoc keyClassName = meta.getPrimaryKey().getClassName();
1012        keyClassName = SimpleCatalog.keyClassName(keyClassName);
1013        DatabaseConfig dbConfig = getPrimaryConfig(meta);
1014
1015        PrimaryIndex<Object JavaDoc,Object JavaDoc> index = getPrimaryIndex
1016            (Object JavaDoc.class, keyClassName, entityClass, entityClassName);
1017        Database db = index.getDatabase();
1018
1019        EntityBinding binding = index.getEntityBinding();
1020        DatabaseEntry key = new DatabaseEntry();
1021        DatabaseEntry data = new DatabaseEntry();
1022
1023        Cursor readCursor = db.openCursor(null, CursorConfig.READ_UNCOMMITTED);
1024        try {
1025            while (readCursor.getNext(key, data, null) ==
1026                   OperationStatus.SUCCESS) {
1027                if (evolveNeeded(key, data, binding)) {
1028                    Transaction txn = null;
1029                    if (dbConfig.getTransactional()) {
1030                        boolean success = false;
1031                        txn = env.beginTransaction(null, null);
1032                    }
1033                    boolean doCommit = false;
1034                    Cursor writeCursor = null;
1035                    try {
1036                        writeCursor = db.openCursor(txn, null);
1037                        if (writeCursor.getSearchKey
1038                                (key, data, LockMode.RMW) ==
1039                                OperationStatus.SUCCESS) {
1040                            boolean written = false;
1041                            if (evolveNeeded(key, data, binding)) {
1042                                writeCursor.putCurrent(data);
1043                                written = true;
1044                            }
1045                            if (listener != null) {
1046                                EvolveInternal.updateEvent
1047                                    (event, entityClassName, 1,
1048                                     written ? 1 : 0);
1049                                if (!listener.evolveProgress(event)) {
1050                                    break;
1051                                }
1052                            }
1053                        }
1054                    } finally {
1055                        if (writeCursor != null) {
1056                            writeCursor.close();
1057                        }
1058                        if (txn != null) {
1059                            if (doCommit) {
1060                                txn.commit();
1061                            } else {
1062                                txn.abort();
1063                            }
1064                        }
1065                    }
1066                }
1067            }
1068        } finally {
1069            readCursor.close();
1070        }
1071    }
1072
1073    /**
1074     * Checks whether the given data is in the current format by translating it
1075     * to/from an object. If true is returned, data is updated.
1076     */

1077    private boolean evolveNeeded(DatabaseEntry key,
1078                                 DatabaseEntry data,
1079                                 EntityBinding binding) {
1080        Object JavaDoc entity = binding.entryToObject(key, data);
1081        DatabaseEntry newData = new DatabaseEntry();
1082        binding.objectToData(entity, newData);
1083        if (data.equals(newData)) {
1084            return false;
1085        } else {
1086            byte[] bytes = newData.getData();
1087            int off = newData.getOffset();
1088            int size = newData.getSize();
1089            data.setData(bytes, off, size);
1090            return true;
1091        }
1092    }
1093
1094    /**
1095     * For unit testing.
1096     */

1097    public static void setSyncHook(SyncHook hook) {
1098        syncHook = hook;
1099    }
1100
1101    /**
1102     * For unit testing.
1103     */

1104    public interface SyncHook {
1105        void onSync(Database db, boolean flushLog);
1106    }
1107}
1108
Popular Tags