2
* See the file LICENSE for redistribution information.
4
* Copyright (c) 2002, 2010 Oracle and/or its affiliates. All rights reserved.
9
package com.sleepycat.persist.impl;
11
import java.util.ArrayList;
12
import java.util.HashMap;
13
import java.util.HashSet;
14
import java.util.IdentityHashMap;
15
import java.util.List;
18
import java.util.WeakHashMap;
20
import com.sleepycat.bind.EntityBinding;
21
import com.sleepycat.bind.tuple.StringBinding;
22
import com.sleepycat.compat.DbCompat;
23
import com.sleepycat.db.Cursor;
24
import com.sleepycat.db.CursorConfig;
25
import com.sleepycat.db.Database;
26
import com.sleepycat.db.DatabaseConfig;
27
import com.sleepycat.db.DatabaseEntry;
28
import com.sleepycat.db.DatabaseException;
29
import com.sleepycat.db.Environment;
30
import com.sleepycat.db.ForeignKeyDeleteAction;
31
import com.sleepycat.db.OperationStatus;
32
import com.sleepycat.db.SecondaryConfig;
33
import com.sleepycat.db.SecondaryDatabase;
34
import com.sleepycat.db.Sequence;
35
import com.sleepycat.db.SequenceConfig;
36
import com.sleepycat.db.Transaction;
37
import com.sleepycat.persist.DatabaseNamer;
38
import com.sleepycat.persist.PrimaryIndex;
39
import com.sleepycat.persist.SecondaryIndex;
40
import com.sleepycat.persist.StoreConfig;
41
import com.sleepycat.persist.StoreExistsException;
42
import com.sleepycat.persist.StoreNotFoundException;
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.IncompatibleClassException;
50
import com.sleepycat.persist.evolve.Mutations;
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.ModelInternal;
55
import com.sleepycat.persist.model.PrimaryKeyMetadata;
56
import com.sleepycat.persist.model.Relationship;
57
import com.sleepycat.persist.model.SecondaryKeyMetadata;
58
import com.sleepycat.persist.raw.RawObject;
59
import com.sleepycat.util.keyrange.KeyRange;
62
* Base implementation for EntityStore and RawStore. The methods here
63
* correspond directly to those in EntityStore; see EntityStore documentation
70
public static final String NAME_SEPARATOR = "#";
71
private static final String NAME_PREFIX = "persist" + NAME_SEPARATOR;
72
private static final String DB_NAME_PREFIX = "com.sleepycat.persist.";
73
private static final String CATALOG_DB = DB_NAME_PREFIX + "formats";
74
private static final String SEQUENCE_DB = DB_NAME_PREFIX + "sequences";
76
private static Map<Environment,Map<String,PersistCatalog>> catalogPool =
77
new WeakHashMap<Environment,Map<String,PersistCatalog>>();
79
/* For unit testing. */
80
private static SyncHook syncHook;
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 storeName;
89
private String storePrefix;
90
private Map<String,PrimaryIndex> priIndexMap;
91
private Map<String,SecondaryIndex> secIndexMap;
92
private Map<String,DatabaseConfig> priConfigMap;
93
private Map<String,SecondaryConfig> secConfigMap;
94
private Map<String,PersistKeyBinding> keyBindingMap;
95
private Map<String,Sequence> sequenceMap;
96
private Map<String,SequenceConfig> sequenceConfigMap;
97
private Database sequenceDb;
98
private IdentityHashMap<Database,Object> deferredWriteDatabases;
99
private Map<String,Set<String>> inverseRelatedEntityMap;
101
public Store(Environment env,
105
throws StoreExistsException,
106
StoreNotFoundException,
107
IncompatibleClassException,
111
this.storeName = storeName;
112
this.rawAccess = rawAccess;
114
if (env == null || storeName == null) {
115
throw new NullPointerException
116
("env and storeName parameters must not be null");
118
if (config != null) {
119
model = config.getModel();
120
mutations = config.getMutations();
122
if (config == null) {
123
storeConfig = StoreConfig.DEFAULT;
125
storeConfig = config.cloneConfig();
128
storePrefix = NAME_PREFIX + storeName + NAME_SEPARATOR;
129
priIndexMap = new HashMap<String,PrimaryIndex>();
130
secIndexMap = new HashMap<String,SecondaryIndex>();
131
priConfigMap = new HashMap<String,DatabaseConfig>();
132
secConfigMap = new HashMap<String,SecondaryConfig>();
133
keyBindingMap = new HashMap<String,PersistKeyBinding>();
134
sequenceMap = new HashMap<String,Sequence>();
135
sequenceConfigMap = new HashMap<String,SequenceConfig>();
136
deferredWriteDatabases = new IdentityHashMap<Database,Object>();
139
/* Open a read-only catalog that uses the stored model. */
141
throw new IllegalArgumentException
142
("A model may not be specified when opening a RawStore");
144
DatabaseConfig dbConfig = new DatabaseConfig();
145
dbConfig.setReadOnly(true);
146
dbConfig.setTransactional
147
(storeConfig.getTransactional());
148
catalog = new PersistCatalog
149
(null, env, storePrefix, storePrefix + CATALOG_DB, dbConfig,
150
model, mutations, rawAccess, this);
152
/* Open the shared catalog that uses the current model. */
153
synchronized (catalogPool) {
154
Map<String,PersistCatalog> catalogMap = catalogPool.get(env);
155
if (catalogMap == null) {
156
catalogMap = new HashMap<String,PersistCatalog>();
157
catalogPool.put(env, catalogMap);
159
catalog = catalogMap.get(storeName);
160
if (catalog != null) {
161
catalog.openExisting();
163
Transaction txn = null;
164
if (storeConfig.getTransactional() &&
165
DbCompat.getThreadTransaction(env) == null) {
166
txn = env.beginTransaction(null, null);
168
boolean success = false;
170
DatabaseConfig dbConfig = new DatabaseConfig();
171
dbConfig.setAllowCreate(storeConfig.getAllowCreate());
172
dbConfig.setExclusiveCreate
173
(storeConfig.getExclusiveCreate());
174
dbConfig.setReadOnly(storeConfig.getReadOnly());
175
dbConfig.setTransactional
176
(storeConfig.getTransactional());
177
DbCompat.setTypeBtree(dbConfig);
178
catalog = new PersistCatalog
179
(txn, env, storePrefix, storePrefix + CATALOG_DB,
180
dbConfig, model, mutations, rawAccess, this);
181
catalogMap.put(storeName, catalog);
196
/* Get the merged mutations from the catalog. */
197
mutations = catalog.getMutations();
200
* If there is no model parameter, use the default or stored model
201
* obtained from the catalog.
203
model = catalog.getResolvedModel();
206
* Give the model a reference to the catalog to fully initialize the
207
* model. Only then may we initialize the Converter mutations, which
208
* themselves may call model methods and expect the model to be fully
211
ModelInternal.setCatalog(model, catalog);
212
for (Converter converter : mutations.getConverters()) {
213
converter.getConversion().initialize(model);
217
* For each existing entity with a relatedEntity reference, create an
218
* inverse map (back pointer) from the class named in the relatedEntity
219
* to the class containing the secondary key. This is used to open the
220
* class containing the secondary key whenever we open the
221
* relatedEntity class, to configure foreign key constraints. Note that
222
* we do not need to update this map as new primary indexes are
223
* created, because opening the new index will setup the foreign key
224
* constraints. [#15358]
226
inverseRelatedEntityMap = new HashMap<String,Set<String>>();
227
List<Format> entityFormats = new ArrayList<Format>();
228
catalog.getEntityFormats(entityFormats);
229
for (Format entityFormat : entityFormats) {
230
EntityMetadata entityMeta = entityFormat.getEntityMetadata();
231
for (SecondaryKeyMetadata secKeyMeta :
232
entityMeta.getSecondaryKeys().values()) {
233
String relatedClsName = secKeyMeta.getRelatedEntity();
234
if (relatedClsName != null) {
235
Set<String> inverseClassNames =
236
inverseRelatedEntityMap.get(relatedClsName);
237
if (inverseClassNames == null) {
238
inverseClassNames = new HashSet<String>();
239
inverseRelatedEntityMap.put
240
(relatedClsName, inverseClassNames);
242
inverseClassNames.add(entityMeta.getClassName());
248
public Environment getEnvironment() {
252
public StoreConfig getConfig() {
253
return storeConfig.cloneConfig();
256
public String getStoreName() {
261
public EntityModel getModel() {
265
public Mutations getMutations() {
270
* A getPrimaryIndex with extra parameters for opening a raw store.
271
* primaryKeyClass and entityClass are used for generic typing; for a raw
272
* store, these should always be Object.class and RawObject.class.
273
* primaryKeyClassName is used for consistency checking and should be null
274
* for a raw store only. entityClassName is used to identify the store and
277
public synchronized <PK,E> PrimaryIndex<PK,E>
278
getPrimaryIndex(Class<PK> primaryKeyClass,
279
String primaryKeyClassName,
280
Class<E> entityClass,
281
String entityClassName)
282
throws DatabaseException {
284
assert (rawAccess && entityClass == RawObject.class) ||
285
(!rawAccess && entityClass != RawObject.class);
286
assert (rawAccess && primaryKeyClassName == null) ||
287
(!rawAccess && primaryKeyClassName != null);
291
PrimaryIndex<PK,E> priIndex = priIndexMap.get(entityClassName);
292
if (priIndex == null) {
294
/* Check metadata. */
295
EntityMetadata entityMeta = checkEntityClass(entityClassName);
296
PrimaryKeyMetadata priKeyMeta = entityMeta.getPrimaryKey();
297
if (primaryKeyClassName == null) {
298
primaryKeyClassName = priKeyMeta.getClassName();
300
String expectClsName =
301
SimpleCatalog.keyClassName(priKeyMeta.getClassName());
302
if (!primaryKeyClassName.equals(expectClsName)) {
303
throw new IllegalArgumentException
304
("Wrong primary key class: " + primaryKeyClassName +
305
" Correct class is: " + expectClsName);
309
/* Create bindings. */
310
PersistEntityBinding entityBinding =
311
new PersistEntityBinding(catalog, entityClassName, rawAccess);
312
PersistKeyBinding keyBinding = getKeyBinding(primaryKeyClassName);
314
/* If not read-only, get the primary key sequence. */
315
String seqName = priKeyMeta.getSequenceName();
316
if (!storeConfig.getReadOnly() && seqName != null) {
317
entityBinding.keyAssigner = new PersistKeyAssigner
318
(keyBinding, entityBinding, getSequence(seqName));
322
* Use a single transaction for opening the primary DB and its
323
* secondaries. If opening any secondary fails, abort the
324
* transaction and undo the changes to the state of the store.
325
* Also support undo if the store is non-transactional.
327
Transaction txn = null;
328
DatabaseConfig dbConfig = getPrimaryConfig(entityMeta);
329
if (dbConfig.getTransactional() &&
330
DbCompat.getThreadTransaction(env) == null) {
331
txn = env.beginTransaction(null, null);
333
PrimaryOpenState priOpenState =
334
new PrimaryOpenState(entityClassName);
335
boolean success = false;
338
/* Open the primary database. */
339
String[] fileAndDbNames =
340
parseDbName(storePrefix + entityClassName);
341
Database db = DbCompat.openDatabase
342
(env, txn, fileAndDbNames[0], fileAndDbNames[1], dbConfig);
344
priOpenState.addDatabase(db);
346
/* Create index object. */
347
priIndex = new PrimaryIndex
348
(db, primaryKeyClass, keyBinding, entityClass,
351
/* Update index and database maps. */
352
priIndexMap.put(entityClassName, priIndex);
353
if (DbCompat.getDeferredWrite(dbConfig)) {
354
deferredWriteDatabases.put(db, null);
357
/* If not read-only, open all associated secondaries. */
358
if (!dbConfig.getReadOnly()) {
359
openSecondaryIndexes(txn, entityMeta, priOpenState);
362
* To enable foreign key contratints, also open all primary
363
* indexes referring to this class via a relatedEntity
364
* property in another entity. [#15358]
366
Set<String> inverseClassNames =
367
inverseRelatedEntityMap.get(entityClassName);
368
if (inverseClassNames != null) {
369
for (String relatedClsName : inverseClassNames) {
370
getRelatedIndex(relatedClsName);
384
priOpenState.undoState();
392
* Holds state information about opening a primary index and its secondary
393
* indexes. Used to undo the state of this object if the transaction
394
* opening the primary and secondaries aborts. Also used to close all
395
* databases opened during this process for a non-transactional store.
397
private class PrimaryOpenState {
399
private String entityClassName;
400
private IdentityHashMap<Database,Object> databases;
401
private Set<String> secNames;
403
PrimaryOpenState(String entityClassName) {
404
this.entityClassName = entityClassName;
405
databases = new IdentityHashMap<Database,Object>();
406
secNames = new HashSet<String>();
410
* Save a database that was opening during this operation.
412
void addDatabase(Database db) {
413
databases.put(db, null);
417
* Save the name of a secondary index that was opening during this
420
void addSecondaryName(String secName) {
421
secNames.add(secName);
425
* Reset all state information and closes any databases opened, when
426
* this operation fails. This method should be called for both
427
* transactional and non-transsactional operation.
429
* For transactional operations on JE, we don't strictly need to close
430
* the databases since the transaction abort will do that. However,
431
* closing them is harmless on JE, and required for DB core.
434
for (Database db : databases.keySet()) {
437
} catch (Exception ignored) {
440
priIndexMap.remove(entityClassName);
441
for (String secName : secNames) {
442
secIndexMap.remove(secName);
444
for (Database db : databases.keySet()) {
445
deferredWriteDatabases.remove(db);
451
* Opens a primary index related via a foreign key (relatedEntity).
452
* Related indexes are not opened in the same transaction used by the
453
* caller to open a primary or secondary. It is OK to leave the related
454
* index open when the caller's transaction aborts. It is only important
455
* to open a primary and its secondaries atomically.
457
private PrimaryIndex getRelatedIndex(String relatedClsName)
458
throws DatabaseException {
460
PrimaryIndex relatedIndex = priIndexMap.get(relatedClsName);
461
if (relatedIndex == null) {
462
EntityMetadata relatedEntityMeta =
463
checkEntityClass(relatedClsName);
465
String relatedKeyClsName;
468
relatedCls = RawObject.class;
469
relatedKeyCls = Object.class;
470
relatedKeyClsName = null;
473
relatedCls = EntityModel.classForName(relatedClsName);
474
} catch (ClassNotFoundException e) {
475
throw new IllegalArgumentException
476
("Related entity class not found: " +
479
relatedKeyClsName = SimpleCatalog.keyClassName
480
(relatedEntityMeta.getPrimaryKey().getClassName());
482
SimpleCatalog.keyClassForName(relatedKeyClsName);
486
* Cycles are prevented here by adding primary indexes to the
487
* priIndexMap as soon as they are created, before opening related
490
relatedIndex = getPrimaryIndex
491
(relatedKeyCls, relatedKeyClsName,
492
relatedCls, relatedClsName);
498
* A getSecondaryIndex with extra parameters for opening a raw store.
499
* keyClassName is used for consistency checking and should be null for a
502
public synchronized <SK,PK,E1,E2 extends E1> SecondaryIndex<SK,PK,E2>
503
getSecondaryIndex(PrimaryIndex<PK,E1> primaryIndex,
504
Class<E2> entityClass,
505
String entityClassName,
509
throws DatabaseException {
511
assert (rawAccess && keyClassName == null) ||
512
(!rawAccess && keyClassName != null);
516
EntityMetadata entityMeta = null;
517
SecondaryKeyMetadata secKeyMeta = null;
519
/* Validate the subclass for a subclass index. */
520
if (entityClass != primaryIndex.getEntityClass()) {
521
entityMeta = model.getEntityMetadata(entityClassName);
522
assert entityMeta != null;
523
secKeyMeta = checkSecKey(entityMeta, keyName);
524
String subclassName = entityClass.getName();
525
String declaringClassName = secKeyMeta.getDeclaringClassName();
526
if (!subclassName.equals(declaringClassName)) {
527
throw new IllegalArgumentException
528
("Key for subclass " + subclassName +
529
" is declared in a different class: " +
530
makeSecName(declaringClassName, keyName));
534
* Get/create the subclass format to ensure it is stored in the
535
* catalog, even if no instances of the subclass are stored.
538
catalog.getFormat(entityClass,
539
false /*checkEntitySubclassIndexes*/);
543
* Even though the primary is already open, we can't assume the
544
* secondary is open because we don't automatically open all
545
* secondaries when the primary is read-only. Use auto-commit (a null
546
* transaction) since we're opening only one database.
548
String secName = makeSecName(entityClassName, keyName);
549
SecondaryIndex<SK,PK,E2> secIndex = secIndexMap.get(secName);
550
if (secIndex == null) {
551
if (entityMeta == null) {
552
entityMeta = model.getEntityMetadata(entityClassName);
553
assert entityMeta != null;
555
if (secKeyMeta == null) {
556
secKeyMeta = checkSecKey(entityMeta, keyName);
559
/* Check metadata. */
560
if (keyClassName == null) {
561
keyClassName = getSecKeyClass(secKeyMeta);
563
String expectClsName = getSecKeyClass(secKeyMeta);
564
if (!keyClassName.equals(expectClsName)) {
565
throw new IllegalArgumentException
566
("Wrong secondary key class: " + keyClassName +
567
" Correct class is: " + expectClsName);
571
secIndex = openSecondaryIndex
572
(null, primaryIndex, entityClass, entityMeta,
573
keyClass, keyClassName, secKeyMeta, secName,
574
false /*doNotCreate*/, null /*priOpenState*/);
580
* Opens secondary indexes for a given primary index metadata.
582
private void openSecondaryIndexes(Transaction txn,
583
EntityMetadata entityMeta,
584
PrimaryOpenState priOpenState)
585
throws DatabaseException {
587
String entityClassName = entityMeta.getClassName();
588
PrimaryIndex<Object,Object> priIndex =
589
priIndexMap.get(entityClassName);
590
assert priIndex != null;
591
Class<Object> entityClass = priIndex.getEntityClass();
593
for (SecondaryKeyMetadata secKeyMeta :
594
entityMeta.getSecondaryKeys().values()) {
595
String keyName = secKeyMeta.getKeyName();
596
String secName = makeSecName(entityClassName, keyName);
597
SecondaryIndex<Object,Object,Object> secIndex =
598
secIndexMap.get(secName);
599
if (secIndex == null) {
600
String keyClassName = getSecKeyClass(secKeyMeta);
601
/* RawMode: should not require class. */
603
SimpleCatalog.keyClassForName(keyClassName);
605
(txn, priIndex, entityClass, entityMeta,
606
keyClass, keyClassName, secKeyMeta,
608
(entityClassName, secKeyMeta.getKeyName()),
609
storeConfig.getSecondaryBulkLoad() /*doNotCreate*/,
616
* Opens a secondary index with a given transaction and adds it to the
617
* secIndexMap. We assume that the index is not already open.
619
private <SK,PK,E1,E2 extends E1> SecondaryIndex<SK,PK,E2>
620
openSecondaryIndex(Transaction txn,
621
PrimaryIndex<PK,E1> primaryIndex,
622
Class<E2> entityClass,
623
EntityMetadata entityMeta,
626
SecondaryKeyMetadata secKeyMeta,
629
PrimaryOpenState priOpenState)
630
throws DatabaseException {
632
assert !secIndexMap.containsKey(secName);
633
String[] fileAndDbNames = parseDbName(storePrefix + secName);
634
SecondaryConfig config =
635
getSecondaryConfig(secName, entityMeta, keyClassName, secKeyMeta);
636
Database priDb = primaryIndex.getDatabase();
637
DatabaseConfig priConfig = priDb.getConfig();
639
String relatedClsName = secKeyMeta.getRelatedEntity();
640
if (relatedClsName != null) {
641
PrimaryIndex relatedIndex = getRelatedIndex(relatedClsName);
642
config.setForeignKeyDatabase(relatedIndex.getDatabase());
645
if (config.getTransactional() != priConfig.getTransactional() ||
646
DbCompat.getDeferredWrite(config) !=
647
DbCompat.getDeferredWrite(priConfig) ||
648
config.getReadOnly() != priConfig.getReadOnly()) {
649
throw new IllegalArgumentException
650
("One of these properties was changed to be inconsistent" +
651
" with the associated primary database: " +
652
" Transactional, DeferredWrite, ReadOnly");
655
PersistKeyBinding keyBinding = getKeyBinding(keyClassName);
657
SecondaryDatabase db = openSecondaryDatabase
658
(txn, fileAndDbNames, priDb, config, doNotCreate);
663
SecondaryIndex<SK,PK,E2> secIndex = new SecondaryIndex
664
(db, null, primaryIndex, keyClass, keyBinding);
666
/* Update index and database maps. */
667
secIndexMap.put(secName, secIndex);
668
if (DbCompat.getDeferredWrite(config)) {
669
deferredWriteDatabases.put(db, null);
671
if (priOpenState != null) {
672
priOpenState.addDatabase(db);
673
priOpenState.addSecondaryName(secName);
679
* Open a secondary database, setting AllowCreate, ExclusiveCreate and
680
* AllowPopulate appropriately. We either set all three of these params to
681
* true or all to false. This ensures that we only populate a database
682
* when it is created, never if it just happens to be empty. [#16399]
684
* @param doNotCreate is true when StoreConfig.getSecondaryBulkLoad is true
685
* and we are opening a secondary as a side effect of opening a primary,
686
* i.e., getSecondaryIndex is not being called. If doNotCreate is true and
687
* the database does not exist, we silently ignore the failure to create
688
* the DB and return null. When getSecondaryIndex is subsequently called,
689
* the secondary database will be created and populated from the primary --
692
private SecondaryDatabase
693
openSecondaryDatabase(final Transaction txn,
694
final String[] fileAndDbNames,
695
final Database priDb,
696
final SecondaryConfig config,
697
final boolean doNotCreate)
698
throws DatabaseException {
700
assert config.getAllowPopulate();
701
assert !config.getExclusiveCreate();
702
final boolean saveAllowCreate = config.getAllowCreate();
705
config.setAllowCreate(false);
707
/* First try creating a new database, populate if needed. */
708
if (config.getAllowCreate()) {
709
config.setExclusiveCreate(true);
710
/* AllowPopulate is already set to true. */
711
final SecondaryDatabase db = DbCompat.openSecondaryDatabase
712
(env, txn, fileAndDbNames[0], fileAndDbNames[1], priDb,
718
/* Next try opening an existing database. */
719
config.setAllowCreate(false);
720
config.setAllowPopulate(false);
721
config.setExclusiveCreate(false);
722
final SecondaryDatabase db = DbCompat.openSecondaryDatabase
723
(env, txn, fileAndDbNames[0], fileAndDbNames[1], priDb,
727
config.setAllowPopulate(true);
728
config.setExclusiveCreate(false);
729
config.setAllowCreate(saveAllowCreate);
734
* Checks that all secondary indexes defined in the given entity metadata
735
* are already open. This method is called when a new entity subclass
736
* is encountered when an instance of that class is stored. [#16399]
738
* @throws IllegalArgumentException if a secondary is not open.
741
checkEntitySubclassSecondaries(final EntityMetadata entityMeta,
742
final String subclassName)
743
throws DatabaseException {
745
if (storeConfig.getSecondaryBulkLoad()) {
749
final String entityClassName = entityMeta.getClassName();
751
for (final SecondaryKeyMetadata secKeyMeta :
752
entityMeta.getSecondaryKeys().values()) {
753
final String keyName = secKeyMeta.getKeyName();
754
final String secName = makeSecName(entityClassName, keyName);
755
if (!secIndexMap.containsKey(secName)) {
756
throw new IllegalArgumentException
757
("Entity subclasses defining a secondary key must be " +
758
"registered by calling EntityModel.registerClass or " +
759
"EntityStore.getSubclassIndex before storing an " +
760
"instance of the subclass: " + subclassName);
766
public void truncateClass(Class entityClass)
767
throws DatabaseException {
769
truncateClass(null, entityClass);
772
public synchronized void truncateClass(Transaction txn, Class entityClass)
773
throws DatabaseException {
777
/* Close primary and secondary databases. */
778
closeClass(entityClass);
780
String clsName = entityClass.getName();
781
EntityMetadata entityMeta = checkEntityClass(clsName);
784
* Truncate the primary first and let any exceptions propogate
785
* upwards. Then remove each secondary, only throwing the first
788
boolean primaryExists = truncateIfExists(txn, storePrefix + clsName);
790
DatabaseException firstException = null;
791
for (SecondaryKeyMetadata keyMeta :
792
entityMeta.getSecondaryKeys().values()) {
793
/* Ignore secondaries that do not exist. */
797
makeSecName(clsName, keyMeta.getKeyName()));
799
if (firstException != null) {
800
throw firstException;
805
private boolean truncateIfExists(Transaction txn, String dbName)
806
throws DatabaseException {
808
String[] fileAndDbNames = parseDbName(dbName);
809
return DbCompat.truncateDatabase
810
(env, txn, fileAndDbNames[0], fileAndDbNames[1]);
813
private boolean removeIfExists(Transaction txn, String dbName)
814
throws DatabaseException {
816
String[] fileAndDbNames = parseDbName(dbName);
817
return DbCompat.removeDatabase
818
(env, txn, fileAndDbNames[0], fileAndDbNames[1]);
821
public synchronized void closeClass(Class entityClass)
822
throws DatabaseException {
825
String clsName = entityClass.getName();
826
EntityMetadata entityMeta = checkEntityClass(clsName);
828
PrimaryIndex priIndex = priIndexMap.get(clsName);
829
if (priIndex != null) {
830
/* Close the secondaries first. */
831
DatabaseException firstException = null;
832
for (SecondaryKeyMetadata keyMeta :
833
entityMeta.getSecondaryKeys().values()) {
835
String secName = makeSecName(clsName, keyMeta.getKeyName());
836
SecondaryIndex secIndex = secIndexMap.get(secName);
837
if (secIndex != null) {
838
Database db = secIndex.getDatabase();
839
firstException = closeDb(db, firstException);
841
closeDb(secIndex.getKeysDatabase(), firstException);
842
secIndexMap.remove(secName);
843
deferredWriteDatabases.remove(db);
846
/* Close the primary last. */
847
Database db = priIndex.getDatabase();
848
firstException = closeDb(db, firstException);
849
priIndexMap.remove(clsName);
850
deferredWriteDatabases.remove(db);
852
/* Throw the first exception encountered. */
853
if (firstException != null) {
854
throw firstException;
859
public synchronized void close()
860
throws DatabaseException {
862
if (catalog == null) {
866
DatabaseException firstException = null;
869
boolean allClosed = catalog.close();
872
synchronized (catalogPool) {
873
Map<String,PersistCatalog> catalogMap =
874
catalogPool.get(env);
875
assert catalogMap != null;
876
if (catalog.close()) {
877
/* Remove when the reference count goes to zero. */
878
catalogMap.remove(storeName);
883
} catch (DatabaseException e) {
884
if (firstException == null) {
888
for (Sequence seq : sequenceMap.values()) {
891
} catch (DatabaseException e) {
892
if (firstException == null) {
897
firstException = closeDb(sequenceDb, firstException);
898
for (SecondaryIndex index : secIndexMap.values()) {
899
firstException = closeDb(index.getDatabase(), firstException);
900
firstException = closeDb(index.getKeysDatabase(), firstException);
902
for (PrimaryIndex index : priIndexMap.values()) {
903
firstException = closeDb(index.getDatabase(), firstException);
905
if (firstException != null) {
906
throw firstException;
910
public synchronized Sequence getSequence(String name)
911
throws DatabaseException {
915
if (storeConfig.getReadOnly()) {
916
throw new IllegalStateException("Store is read-only");
919
Sequence seq = sequenceMap.get(name);
921
if (sequenceDb == null) {
922
String[] fileAndDbNames =
923
parseDbName(storePrefix + SEQUENCE_DB);
924
DatabaseConfig dbConfig = new DatabaseConfig();
925
dbConfig.setTransactional(storeConfig.getTransactional());
926
dbConfig.setAllowCreate(true);
927
DbCompat.setTypeBtree(dbConfig);
928
sequenceDb = DbCompat.openDatabase
929
(env, null/*txn*/, fileAndDbNames[0],
930
fileAndDbNames[1], dbConfig);
931
assert sequenceDb != null;
933
DatabaseEntry entry = new DatabaseEntry();
934
StringBinding.stringToEntry(name, entry);
935
seq = sequenceDb.openSequence(null, entry,
936
getSequenceConfig(name));
937
sequenceMap.put(name, seq);
942
public synchronized SequenceConfig getSequenceConfig(String name) {
944
SequenceConfig config = sequenceConfigMap.get(name);
945
if (config == null) {
946
config = new SequenceConfig();
947
config.setInitialValue(1);
948
config.setRange(1, Long.MAX_VALUE);
949
config.setCacheSize(100);
950
config.setAutoCommitNoSync(true);
951
config.setAllowCreate(!storeConfig.getReadOnly());
952
sequenceConfigMap.put(name, config);
957
public synchronized void setSequenceConfig(String name,
958
SequenceConfig config) {
960
if (config.getExclusiveCreate() ||
961
config.getAllowCreate() == storeConfig.getReadOnly()) {
962
throw new IllegalArgumentException
963
("One of these properties was illegally changed: " +
964
"AllowCreate, ExclusiveCreate");
966
sequenceConfigMap.put(name, config);
969
public synchronized DatabaseConfig getPrimaryConfig(Class entityClass) {
971
String clsName = entityClass.getName();
972
EntityMetadata meta = checkEntityClass(clsName);
973
return getPrimaryConfig(meta).cloneConfig();
976
private synchronized DatabaseConfig getPrimaryConfig(EntityMetadata meta) {
977
String clsName = meta.getClassName();
978
DatabaseConfig config = priConfigMap.get(clsName);
979
if (config == null) {
980
config = new DatabaseConfig();
981
config.setTransactional(storeConfig.getTransactional());
982
config.setAllowCreate(!storeConfig.getReadOnly());
983
config.setReadOnly(storeConfig.getReadOnly());
984
DbCompat.setTypeBtree(config);
985
setBtreeComparator(config, meta.getPrimaryKey().getClassName());
986
priConfigMap.put(clsName, config);
991
public synchronized void setPrimaryConfig(Class entityClass,
992
DatabaseConfig config) {
994
String clsName = entityClass.getName();
995
if (priIndexMap.containsKey(clsName)) {
996
throw new IllegalStateException
997
("Cannot set config after DB is open");
999
EntityMetadata meta = checkEntityClass(clsName);
1000
DatabaseConfig dbConfig = getPrimaryConfig(meta);
1001
if (config.getExclusiveCreate() ||
1002
config.getAllowCreate() == config.getReadOnly() ||
1003
config.getSortedDuplicates() ||
1004
config.getBtreeComparator() != dbConfig.getBtreeComparator()) {
1005
throw new IllegalArgumentException
1006
("One of these properties was illegally changed: " +
1007
"AllowCreate, ExclusiveCreate, SortedDuplicates, Temporary " +
1008
"or BtreeComparator, ");
1010
if (!DbCompat.isTypeBtree(config)) {
1011
throw new IllegalArgumentException("Only type BTREE allowed");
1013
priConfigMap.put(clsName, config);
1016
public synchronized SecondaryConfig getSecondaryConfig(Class entityClass,
1019
String entityClsName = entityClass.getName();
1020
EntityMetadata entityMeta = checkEntityClass(entityClsName);
1021
SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta, keyName);
1022
String keyClassName = getSecKeyClass(secKeyMeta);
1023
String secName = makeSecName(entityClass.getName(), keyName);
1024
return (SecondaryConfig) getSecondaryConfig
1025
(secName, entityMeta, keyClassName, secKeyMeta).cloneConfig();
1028
private SecondaryConfig getSecondaryConfig(String secName,
1029
EntityMetadata entityMeta,
1030
String keyClassName,
1031
SecondaryKeyMetadata
1033
SecondaryConfig config = secConfigMap.get(secName);
1034
if (config == null) {
1035
/* Set common properties to match the primary DB. */
1036
DatabaseConfig priConfig = getPrimaryConfig(entityMeta);
1037
config = new SecondaryConfig();
1038
config.setTransactional(priConfig.getTransactional());
1039
config.setAllowCreate(!priConfig.getReadOnly());
1040
config.setReadOnly(priConfig.getReadOnly());
1041
DbCompat.setTypeBtree(config);
1042
/* Set secondary properties based on metadata. */
1043
config.setAllowPopulate(true);
1044
Relationship rel = secKeyMeta.getRelationship();
1045
config.setSortedDuplicates(rel == Relationship.MANY_TO_ONE ||
1046
rel == Relationship.MANY_TO_MANY);
1047
setBtreeComparator(config, keyClassName);
1048
PersistKeyCreator keyCreator = new PersistKeyCreator
1049
(catalog, entityMeta, keyClassName, secKeyMeta, rawAccess);
1050
if (rel == Relationship.ONE_TO_MANY ||
1051
rel == Relationship.MANY_TO_MANY) {
1052
config.setMultiKeyCreator(keyCreator);
1054
config.setKeyCreator(keyCreator);
1056
DeleteAction deleteAction = secKeyMeta.getDeleteAction();
1057
if (deleteAction != null) {
1058
ForeignKeyDeleteAction baseDeleteAction;
1059
switch (deleteAction) {
1061
baseDeleteAction = ForeignKeyDeleteAction.ABORT;
1064
baseDeleteAction = ForeignKeyDeleteAction.CASCADE;
1067
baseDeleteAction = ForeignKeyDeleteAction.NULLIFY;
1070
throw new IllegalStateException(deleteAction.toString());
1072
config.setForeignKeyDeleteAction(baseDeleteAction);
1073
if (deleteAction == DeleteAction.NULLIFY) {
1074
config.setForeignMultiKeyNullifier(keyCreator);
1077
secConfigMap.put(secName, config);
1082
public synchronized void setSecondaryConfig(Class entityClass,
1084
SecondaryConfig config) {
1086
String entityClsName = entityClass.getName();
1087
EntityMetadata entityMeta = checkEntityClass(entityClsName);
1088
SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta, keyName);
1089
String keyClassName = getSecKeyClass(secKeyMeta);
1090
String secName = makeSecName(entityClass.getName(), keyName);
1091
if (secIndexMap.containsKey(secName)) {
1092
throw new IllegalStateException
1093
("Cannot set config after DB is open");
1095
SecondaryConfig dbConfig =
1096
getSecondaryConfig(secName, entityMeta, keyClassName, secKeyMeta);
1097
if (config.getExclusiveCreate() ||
1098
config.getAllowCreate() == config.getReadOnly() ||
1099
config.getSortedDuplicates() != dbConfig.getSortedDuplicates() ||
1100
config.getBtreeComparator() != dbConfig.getBtreeComparator() ||
1101
config.getDuplicateComparator() != null ||
1102
config.getAllowPopulate() != dbConfig.getAllowPopulate() ||
1103
config.getKeyCreator() != dbConfig.getKeyCreator() ||
1104
config.getMultiKeyCreator() != dbConfig.getMultiKeyCreator() ||
1105
config.getForeignKeyNullifier() !=
1106
dbConfig.getForeignKeyNullifier() ||
1107
config.getForeignMultiKeyNullifier() !=
1108
dbConfig.getForeignMultiKeyNullifier() ||
1109
config.getForeignKeyDeleteAction() !=
1110
dbConfig.getForeignKeyDeleteAction() ||
1111
config.getForeignKeyDatabase() != null) {
1112
throw new IllegalArgumentException
1113
("One of these properties was illegally changed: " +
1114
" AllowCreate, ExclusiveCreate, SortedDuplicates," +
1115
" BtreeComparator, DuplicateComparator, Temporary," +
1116
" AllowPopulate, KeyCreator, MultiKeyCreator," +
1117
" ForeignKeyNullifer, ForeignMultiKeyNullifier," +
1118
" ForeignKeyDeleteAction, ForeignKeyDatabase");
1120
if (!DbCompat.isTypeBtree(config)) {
1121
throw new IllegalArgumentException("Only type BTREE allowed");
1123
secConfigMap.put(secName, config);
1126
private static String makeSecName(String entityClsName, String keyName) {
1127
return entityClsName + NAME_SEPARATOR + keyName;
1130
static String makePriDbName(String storePrefix, String entityClsName) {
1131
return storePrefix + entityClsName;
1134
static String makeSecDbName(String storePrefix,
1135
String entityClsName,
1137
return storePrefix + makeSecName(entityClsName, keyName);
1141
* Parses a whole DB name and returns an array of 2 strings where element 0
1142
* is the file name (always null for JE, always non-null for DB core) and
1143
* element 1 is the logical DB name (always non-null for JE, may be null
1146
public String[] parseDbName(String wholeName) {
1147
return parseDbName(wholeName, storeConfig.getDatabaseNamer());
1151
* Allows passing a namer to a static method for testing.
1153
public static String[] parseDbName(String wholeName, DatabaseNamer namer) {
1154
String[] result = new String[2];
1155
if (DbCompat.SEPARATE_DATABASE_FILES) {
1156
String[] splitName = wholeName.split(NAME_SEPARATOR);
1157
assert splitName.length == 3 || splitName.length == 4 : wholeName;
1158
assert splitName[0].equals("persist") : wholeName;
1159
String storeName = splitName[1];
1160
String clsName = splitName[2];
1161
String keyName = (splitName.length > 3) ? splitName[3] : null;
1162
result[0] = namer.getFileName(storeName, clsName, keyName);
1166
result[1] = wholeName;
1172
* Creates a message identifying the database from the pair of strings
1173
* returned by parseDbName.
1175
String getDbNameMessage(String[] names) {
1176
if (DbCompat.SEPARATE_DATABASE_FILES) {
1177
return "file: " + names[0];
1179
return "database: " + names[1];
1183
private void checkOpen() {
1184
if (catalog == null) {
1185
throw new IllegalStateException("Store has been closed");
1189
private EntityMetadata checkEntityClass(String clsName) {
1190
EntityMetadata meta = model.getEntityMetadata(clsName);
1192
throw new IllegalArgumentException
1193
("Class could not be loaded or is not an entity class: " +
1199
private SecondaryKeyMetadata checkSecKey(EntityMetadata entityMeta,
1201
SecondaryKeyMetadata secKeyMeta =
1202
entityMeta.getSecondaryKeys().get(keyName);
1203
if (secKeyMeta == null) {
1204
throw new IllegalArgumentException
1205
("Not a secondary key: " +
1206
makeSecName(entityMeta.getClassName(), keyName));
1211
private String getSecKeyClass(SecondaryKeyMetadata secKeyMeta) {
1212
String clsName = secKeyMeta.getElementClassName();
1213
if (clsName == null) {
1214
clsName = secKeyMeta.getClassName();
1216
return SimpleCatalog.keyClassName(clsName);
1219
private PersistKeyBinding getKeyBinding(String keyClassName) {
1220
PersistKeyBinding binding = keyBindingMap.get(keyClassName);
1221
if (binding == null) {
1222
binding = new PersistKeyBinding(catalog, keyClassName, rawAccess);
1223
keyBindingMap.put(keyClassName, binding);
1228
private void setBtreeComparator(DatabaseConfig config, String clsName) {
1230
PersistKeyBinding binding = getKeyBinding(clsName);
1231
Format format = binding.keyFormat;
1232
if (format instanceof CompositeKeyFormat) {
1233
Class keyClass = format.getType();
1234
if (Comparable.class.isAssignableFrom(keyClass)) {
1235
config.setBtreeComparator(new PersistComparator(binding));
1241
private DatabaseException closeDb(Database db,
1242
DatabaseException firstException) {
1246
} catch (DatabaseException e) {
1247
if (firstException == null) {
1252
return firstException;
1255
public EvolveStats evolve(EvolveConfig config)
1256
throws DatabaseException {
1259
List<Format> toEvolve = new ArrayList<Format>();
1260
Set<String> configToEvolve = config.getClassesToEvolve();
1261
if (configToEvolve.isEmpty()) {
1262
catalog.getEntityFormats(toEvolve);
1264
for (String name : configToEvolve) {
1265
Format format = catalog.getFormat(name);
1266
if (format == null) {
1267
throw new IllegalArgumentException
1268
("Class to evolve is not persistent: " + name);
1270
if (!format.isEntity()) {
1271
throw new IllegalArgumentException
1272
("Class to evolve is not an entity class: " + name);
1274
toEvolve.add(format);
1278
EvolveEvent event = EvolveInternal.newEvent();
1279
for (Format format : toEvolve) {
1280
if (format.getEvolveNeeded()) {
1281
evolveIndex(format, event, config.getEvolveListener());
1282
format.setEvolveNeeded(false);
1287
return event.getStats();
1290
private void evolveIndex(Format format,
1292
EvolveListener listener)
1293
throws DatabaseException {
1295
/* We may make this configurable later. */
1296
final int WRITES_PER_TXN = 1;
1298
Class entityClass = format.getType();
1299
String entityClassName = format.getClassName();
1300
EntityMetadata meta = model.getEntityMetadata(entityClassName);
1301
String keyClassName = meta.getPrimaryKey().getClassName();
1302
keyClassName = SimpleCatalog.keyClassName(keyClassName);
1303
DatabaseConfig dbConfig = getPrimaryConfig(meta);
1305
PrimaryIndex<Object,Object> index = getPrimaryIndex
1306
(Object.class, keyClassName, entityClass, entityClassName);
1307
Database db = index.getDatabase();
1309
EntityBinding binding = index.getEntityBinding();
1310
DatabaseEntry key = new DatabaseEntry();
1311
DatabaseEntry data = new DatabaseEntry();
1313
CursorConfig cursorConfig = null;
1314
Transaction txn = null;
1315
if (dbConfig.getTransactional()) {
1316
txn = env.beginTransaction(null, null);
1317
cursorConfig = CursorConfig.READ_COMMITTED;
1320
Cursor cursor = null;
1323
cursor = db.openCursor(txn, cursorConfig);
1324
OperationStatus status = cursor.getFirst(key, data, null);
1325
while (status == OperationStatus.SUCCESS) {
1326
boolean oneWritten = false;
1327
if (evolveNeeded(key, data, binding)) {
1328
cursor.putCurrent(data);
1332
/* Update event stats, even if no listener. [#17024] */
1333
EvolveInternal.updateEvent
1334
(event, entityClassName, 1, oneWritten ? 1 : 0);
1335
if (listener != null) {
1336
if (!listener.evolveProgress(event)) {
1340
if (txn != null && nWritten >= WRITES_PER_TXN) {
1345
txn = env.beginTransaction(null, null);
1346
cursor = db.openCursor(txn, cursorConfig);
1347
DatabaseEntry saveKey = KeyRange.copy(key);
1348
status = cursor.getSearchKeyRange(key, data, null);
1349
if (status == OperationStatus.SUCCESS &&
1350
KeyRange.equalBytes(key, saveKey)) {
1351
status = cursor.getNext(key, data, null);
1354
status = cursor.getNext(key, data, null);
1358
if (cursor != null) {
1372
* Checks whether the given data is in the current format by translating it
1373
* to/from an object. If true is returned, data is updated.
1375
private boolean evolveNeeded(DatabaseEntry key,
1377
EntityBinding binding) {
1378
Object entity = binding.entryToObject(key, data);
1379
DatabaseEntry newData = new DatabaseEntry();
1380
binding.objectToData(entity, newData);
1381
if (data.equals(newData)) {
1384
byte[] bytes = newData.getData();
1385
int off = newData.getOffset();
1386
int size = newData.getSize();
1387
data.setData(bytes, off, size);
1395
public static void setSyncHook(SyncHook hook) {
1402
public interface SyncHook {
1403
void onSync(Database db, boolean flushLog);