com.sleepycat.persist.impl.Store Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of je Show documentation
Show all versions of je Show documentation
Berkley Database Java Edition - build and runtime support.
/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2002, 2013 Oracle and/or its affiliates. All rights reserved.
*
*/
package com.sleepycat.persist.impl;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import com.sleepycat.bind.EntityBinding;
import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
/* */
import com.sleepycat.je.DbInternal;
import com.sleepycat.je.Durability;
/* */
import com.sleepycat.je.Environment;
import com.sleepycat.je.ForeignKeyDeleteAction;
/* */
import com.sleepycat.je.LockConflictException;
/* */
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.Sequence;
import com.sleepycat.je.SequenceConfig;
/* */
import com.sleepycat.je.SequenceExistsException;
import com.sleepycat.je.SequenceNotFoundException;
/* */
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
/* */
import com.sleepycat.je.rep.NoConsistencyRequiredPolicy;
/* */
import com.sleepycat.je.utilint.IdentityHashMap;
import com.sleepycat.persist.DatabaseNamer;
import com.sleepycat.persist.IndexNotAvailableException;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;
import com.sleepycat.persist.StoreConfig;
import com.sleepycat.persist.StoreExistsException;
import com.sleepycat.persist.StoreNotFoundException;
import com.sleepycat.persist.evolve.EvolveConfig;
import com.sleepycat.persist.evolve.EvolveEvent;
import com.sleepycat.persist.evolve.EvolveInternal;
import com.sleepycat.persist.evolve.EvolveListener;
import com.sleepycat.persist.evolve.EvolveStats;
import com.sleepycat.persist.evolve.IncompatibleClassException;
import com.sleepycat.persist.evolve.Mutations;
import com.sleepycat.persist.model.DeleteAction;
import com.sleepycat.persist.model.EntityMetadata;
import com.sleepycat.persist.model.EntityModel;
import com.sleepycat.persist.model.PrimaryKeyMetadata;
import com.sleepycat.persist.model.Relationship;
import com.sleepycat.persist.model.SecondaryKeyMetadata;
import com.sleepycat.persist.raw.RawObject;
import com.sleepycat.util.keyrange.KeyRange;
import com.sleepycat.util.RuntimeExceptionWrapper;
/**
* Base implementation for EntityStore and RawStore. The methods here
* correspond directly to those in EntityStore; see EntityStore documentation
* for details.
*
* @author Mark Hayes
*/
public class Store {
public static final String NAME_SEPARATOR = "#";
private static final String NAME_PREFIX = "persist" + NAME_SEPARATOR;
private static final String DB_NAME_PREFIX = "com.sleepycat.persist.";
private static final String CATALOG_DB = DB_NAME_PREFIX + "formats";
private static final String SEQUENCE_DB = DB_NAME_PREFIX + "sequences";
private static Map> catalogPool =
new WeakHashMap>();
/* For unit testing. */
private static SyncHook syncHook;
public static boolean expectFlush;
private final Environment env;
private final boolean rawAccess;
private volatile PersistCatalog catalog;
private EntityModel model;
private final StoreConfig storeConfig;
private final String storeName;
private final String storePrefix;
private final Map priIndexMap;
private final Map secIndexMap;
private final Map priConfigMap;
private final Map secConfigMap;
private final Map keyBindingMap;
private Database sequenceDb;
private final Map sequenceMap;
private final Map sequenceConfigMap;
private final IdentityHashMap deferredWriteDatabases;
private final Map> inverseRelatedEntityMap;
private final TransactionConfig autoCommitTxnConfig;
private final TransactionConfig autoCommitNoWaitTxnConfig;
public Store(Environment env,
String storeName,
StoreConfig config,
boolean rawAccess)
throws StoreExistsException,
StoreNotFoundException,
IncompatibleClassException,
DatabaseException {
this.env = env;
this.storeName = storeName;
this.rawAccess = rawAccess;
if (env == null || storeName == null) {
throw new NullPointerException
("env and storeName parameters must not be null");
}
storeConfig = (config != null) ?
config.clone() :
StoreConfig.DEFAULT;
autoCommitTxnConfig = new TransactionConfig();
autoCommitNoWaitTxnConfig = new TransactionConfig();
autoCommitNoWaitTxnConfig.setNoWait(true);
/* */
if (!storeConfig.getReplicated()) {
final Durability envDurability = env.getConfig().getDurability();
configForNonRepDb(autoCommitTxnConfig, envDurability);
configForNonRepDb(autoCommitNoWaitTxnConfig, envDurability);
}
/* */
model = config.getModel();
storePrefix = NAME_PREFIX + storeName + NAME_SEPARATOR;
priIndexMap = new HashMap();
secIndexMap = new HashMap();
priConfigMap = new HashMap();
secConfigMap = new HashMap();
keyBindingMap = new HashMap();
sequenceMap = new HashMap();
sequenceConfigMap = new HashMap();
deferredWriteDatabases = new IdentityHashMap();
if (rawAccess) {
/* Open a read-only catalog that uses the stored model. */
if (model != null) {
throw new IllegalArgumentException
("A model may not be specified when opening a RawStore");
}
DatabaseConfig dbConfig = new DatabaseConfig();
/* */
DbInternal.setReplicated(dbConfig, storeConfig.getReplicated());
/* */
dbConfig.setReadOnly(true);
dbConfig.setTransactional
(storeConfig.getTransactional());
catalog = new PersistCatalog
(env, storePrefix, storePrefix + CATALOG_DB, dbConfig,
null /*model*/, config.getMutations(), rawAccess, this);
} else {
/* Open the shared catalog that uses the current model. */
synchronized (catalogPool) {
Map catalogMap = catalogPool.get(env);
if (catalogMap == null) {
catalogMap = new HashMap();
catalogPool.put(env, catalogMap);
}
catalog = catalogMap.get(storeName);
if (catalog != null) {
catalog.openExisting();
} else {
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(storeConfig.getAllowCreate());
dbConfig.setExclusiveCreate
(storeConfig.getExclusiveCreate());
/* */
dbConfig.setTemporary(storeConfig.getTemporary());
DbInternal.setReplicated(dbConfig,
storeConfig.getReplicated());
/* */
dbConfig.setReadOnly(storeConfig.getReadOnly());
dbConfig.setTransactional
(storeConfig.getTransactional());
DbCompat.setTypeBtree(dbConfig);
catalog = new PersistCatalog
(env, storePrefix, storePrefix + CATALOG_DB, dbConfig,
model, config.getMutations(), rawAccess, this);
catalogMap.put(storeName, catalog);
}
}
}
/*
* If there is no model parameter, use the default or stored model
* obtained from the catalog.
*/
model = catalog.getResolvedModel();
/*
* For each existing entity with a relatedEntity reference, create an
* inverse map (back pointer) from the class named in the relatedEntity
* to the class containing the secondary key. This is used to open the
* class containing the secondary key whenever we open the
* relatedEntity class, to configure foreign key constraints. Note that
* we do not need to update this map as new primary indexes are
* created, because opening the new index will setup the foreign key
* constraints. [#15358]
*/
inverseRelatedEntityMap = new HashMap>();
List entityFormats = new ArrayList();
catalog.getEntityFormats(entityFormats);
for (Format entityFormat : entityFormats) {
EntityMetadata entityMeta = entityFormat.getEntityMetadata();
for (SecondaryKeyMetadata secKeyMeta :
entityMeta.getSecondaryKeys().values()) {
String relatedClsName = secKeyMeta.getRelatedEntity();
if (relatedClsName != null) {
Set inverseClassNames =
inverseRelatedEntityMap.get(relatedClsName);
if (inverseClassNames == null) {
inverseClassNames = new HashSet();
inverseRelatedEntityMap.put
(relatedClsName, inverseClassNames);
}
inverseClassNames.add(entityMeta.getClassName());
}
}
}
}
public Environment getEnvironment() {
return env;
}
public StoreConfig getConfig() {
return storeConfig.clone();
}
public String getStoreName() {
return storeName;
}
/* */
public static Set getStoreNames(Environment env)
throws DatabaseException {
Set set = new HashSet();
for (Object o : env.getDatabaseNames()) {
String s = (String) o;
if (s.startsWith(NAME_PREFIX)) {
int start = NAME_PREFIX.length();
int end = s.indexOf(NAME_SEPARATOR, start);
set.add(s.substring(start, end));
}
}
return set;
}
/* */
/**
* For unit testing.
*/
public boolean isReplicaUpgradeMode() {
return catalog.isReplicaUpgradeMode();
}
public EntityModel getModel() {
return model;
}
public Mutations getMutations() {
return catalog.getMutations();
}
/**
* A getPrimaryIndex with extra parameters for opening a raw store.
* primaryKeyClass and entityClass are used for generic typing; for a raw
* store, these should always be Object.class and RawObject.class.
* primaryKeyClassName is used for consistency checking and should be null
* for a raw store only. entityClassName is used to identify the store and
* may not be null.
*/
public synchronized PrimaryIndex
getPrimaryIndex(Class primaryKeyClass,
String primaryKeyClassName,
Class entityClass,
String entityClassName)
throws DatabaseException, IndexNotAvailableException {
assert (rawAccess && entityClass == RawObject.class) ||
(!rawAccess && entityClass != RawObject.class);
assert (rawAccess && primaryKeyClassName == null) ||
(!rawAccess && primaryKeyClassName != null);
checkOpen();
InternalPrimaryIndex priIndex =
priIndexMap.get(entityClassName);
if (priIndex == null) {
/* Check metadata. */
EntityMetadata entityMeta = checkEntityClass(entityClassName);
PrimaryKeyMetadata priKeyMeta = entityMeta.getPrimaryKey();
if (primaryKeyClassName == null) {
primaryKeyClassName = priKeyMeta.getClassName();
} else {
String expectClsName =
SimpleCatalog.keyClassName(priKeyMeta.getClassName());
if (!primaryKeyClassName.equals(expectClsName)) {
throw new IllegalArgumentException
("Wrong primary key class: " + primaryKeyClassName +
" Correct class is: " + expectClsName);
}
}
/* Create bindings. */
PersistEntityBinding entityBinding =
new PersistEntityBinding(catalog, entityClassName, rawAccess);
PersistKeyBinding keyBinding = getKeyBinding(primaryKeyClassName);
/* If not read-only, get the primary key sequence. */
String seqName = priKeyMeta.getSequenceName();
if (!storeConfig.getReadOnly() && seqName != null) {
entityBinding.keyAssigner = new PersistKeyAssigner
(keyBinding, entityBinding, getSequence(seqName));
}
/*
* Use a single transaction for opening the primary DB and its
* secondaries. If opening any secondary fails, abort the
* transaction and undo the changes to the state of the store.
* Also support undo if the store is non-transactional.
*
* Use a no-wait transaction to avoid blocking on a Replica while
* attempting to open an index that is currently being populated
* via the replication stream from the Master.
*/
Transaction txn = null;
DatabaseConfig dbConfig = getPrimaryConfig(entityMeta);
if (dbConfig.getTransactional() &&
DbCompat.getThreadTransaction(env) == null) {
txn = env.beginTransaction(null, autoCommitNoWaitTxnConfig);
}
PrimaryOpenState priOpenState =
new PrimaryOpenState(entityClassName);
final boolean saveAllowCreate = dbConfig.getAllowCreate();
boolean success = false;
try {
/*
* The AllowCreate setting is false in read-only / Replica
* upgrade mode. In this mode new primaries are not available.
* They can be opened later when the upgrade is complete on the
* Master, by calling getSecondaryIndex. [#16655]
*/
if (catalog.isReadOnly()) {
dbConfig.setAllowCreate(false);
}
/*
* Open the primary database. Account for database renaming
* by calling getDatabaseClassName. The dbClassName will be
* null if the format has not yet been stored. [#16655].
*/
Database db = null;
final String dbClassName =
catalog.getDatabaseClassName(entityClassName);
if (dbClassName != null) {
final String[] fileAndDbNames =
parseDbName(storePrefix + dbClassName);
/* */
try {
/* */
db = DbCompat.openDatabase(env, txn, fileAndDbNames[0],
fileAndDbNames[1],
dbConfig);
/* */
} catch (LockConflictException e) {
/* Treat this as if the database does not exist. */
}
/* */
}
if (db == null) {
throw new IndexNotAvailableException
("PrimaryIndex not yet available on this Replica, " +
"entity class: " + entityClassName);
}
priOpenState.addDatabase(db);
/* Create index object. */
priIndex = new InternalPrimaryIndex(db, primaryKeyClass,
keyBinding, entityClass,
entityBinding);
/* Update index and database maps. */
priIndexMap.put(entityClassName, priIndex);
if (DbCompat.getDeferredWrite(dbConfig)) {
deferredWriteDatabases.put(db, null);
}
/* If not read-only, open all associated secondaries. */
if (!dbConfig.getReadOnly()) {
openSecondaryIndexes(txn, entityMeta, priOpenState);
/*
* To enable foreign key contraints, also open all primary
* indexes referring to this class via a relatedEntity
* property in another entity. [#15358]
*/
Set inverseClassNames =
inverseRelatedEntityMap.get(entityClassName);
if (inverseClassNames != null) {
for (String relatedClsName : inverseClassNames) {
getRelatedIndex(relatedClsName);
}
}
}
success = true;
} finally {
dbConfig.setAllowCreate(saveAllowCreate);
if (success) {
if (txn != null) {
txn.commit();
}
} else {
if (txn != null) {
txn.abort();
}
priOpenState.undoState();
}
}
}
return priIndex;
}
/**
* Holds state information about opening a primary index and its secondary
* indexes. Used to undo the state of this object if the transaction
* opening the primary and secondaries aborts. Also used to close all
* databases opened during this process for a non-transactional store.
*/
private class PrimaryOpenState {
private String entityClassName;
private IdentityHashMap databases;
private Set secNames;
PrimaryOpenState(String entityClassName) {
this.entityClassName = entityClassName;
databases = new IdentityHashMap();
secNames = new HashSet();
}
/**
* Save a database that was opening during this operation.
*/
void addDatabase(Database db) {
databases.put(db, null);
}
/**
* Save the name of a secondary index that was opening during this
* operation.
*/
void addSecondaryName(String secName) {
secNames.add(secName);
}
/**
* Reset all state information and closes any databases opened, when
* this operation fails. This method should be called for both
* transactional and non-transsactional operation.
*
* For transactional operations on JE, we don't strictly need to close
* the databases since the transaction abort will do that. However,
* closing them is harmless on JE, and required for DB core.
*/
void undoState() {
for (Database db : databases.keySet()) {
try {
db.close();
} catch (Exception ignored) {
}
}
priIndexMap.remove(entityClassName);
for (String secName : secNames) {
secIndexMap.remove(secName);
}
for (Database db : databases.keySet()) {
deferredWriteDatabases.remove(db);
}
}
}
/**
* Opens a primary index related via a foreign key (relatedEntity).
* Related indexes are not opened in the same transaction used by the
* caller to open a primary or secondary. It is OK to leave the related
* index open when the caller's transaction aborts. It is only important
* to open a primary and its secondaries atomically.
*/
private PrimaryIndex getRelatedIndex(String relatedClsName)
throws DatabaseException {
PrimaryIndex relatedIndex = priIndexMap.get(relatedClsName);
if (relatedIndex == null) {
EntityMetadata relatedEntityMeta =
checkEntityClass(relatedClsName);
Class relatedKeyCls;
String relatedKeyClsName;
Class relatedCls;
if (rawAccess) {
relatedCls = RawObject.class;
relatedKeyCls = Object.class;
relatedKeyClsName = null;
} else {
try {
relatedCls = catalog.resolveClass(relatedClsName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException
("Related entity class not found: " +
relatedClsName);
}
relatedKeyClsName = SimpleCatalog.keyClassName
(relatedEntityMeta.getPrimaryKey().getClassName());
relatedKeyCls = catalog.resolveKeyClass(relatedKeyClsName);
}
/*
* Cycles are prevented here by adding primary indexes to the
* priIndexMap as soon as they are created, before opening related
* indexes.
*/
relatedIndex = getPrimaryIndex
(relatedKeyCls, relatedKeyClsName,
relatedCls, relatedClsName);
}
return relatedIndex;
}
/**
* A getSecondaryIndex with extra parameters for opening a raw store.
* keyClassName is used for consistency checking and should be null for a
* raw store only.
*/
public synchronized SecondaryIndex
getSecondaryIndex(PrimaryIndex primaryIndex,
Class entityClass,
String entityClassName,
Class keyClass,
String keyClassName,
String keyName)
throws DatabaseException, IndexNotAvailableException {
assert (rawAccess && keyClassName == null) ||
(!rawAccess && keyClassName != null);
checkOpen();
EntityMetadata entityMeta = null;
SecondaryKeyMetadata secKeyMeta = null;
/* Validate the subclass for a subclass index. */
if (entityClass != primaryIndex.getEntityClass()) {
entityMeta = model.getEntityMetadata(entityClassName);
assert entityMeta != null;
secKeyMeta = checkSecKey(entityMeta, keyName);
String subclassName = entityClass.getName();
String declaringClassName = secKeyMeta.getDeclaringClassName();
if (!subclassName.equals(declaringClassName)) {
throw new IllegalArgumentException
("Key for subclass " + subclassName +
" is declared in a different class: " +
makeSecName(declaringClassName, keyName));
}
/*
* Get/create the subclass format to ensure it is stored in the
* catalog, even if no instances of the subclass are stored.
* [#16399]
*/
try {
catalog.getFormat(entityClass,
false /*checkEntitySubclassIndexes*/);
} catch (RefreshException e) {
e.refresh();
try {
catalog.getFormat(entityClass,
false /*checkEntitySubclassIndexes*/);
} catch (RefreshException e2) {
throw DbCompat.unexpectedException(e2);
}
}
}
/*
* Even though the primary is already open, we can't assume the
* secondary is open because we don't automatically open all
* secondaries when the primary is read-only. Use auto-commit (a null
* transaction) since we're opening only one database.
*/
String secName = makeSecName(entityClassName, keyName);
InternalSecondaryIndex secIndex = secIndexMap.get(secName);
if (secIndex == null) {
if (entityMeta == null) {
entityMeta = model.getEntityMetadata(entityClassName);
assert entityMeta != null;
}
if (secKeyMeta == null) {
secKeyMeta = checkSecKey(entityMeta, keyName);
}
/* Check metadata. */
if (keyClassName == null) {
keyClassName = getSecKeyClass(secKeyMeta);
} else {
String expectClsName = getSecKeyClass(secKeyMeta);
if (!keyClassName.equals(expectClsName)) {
throw new IllegalArgumentException
("Wrong secondary key class: " + keyClassName +
" Correct class is: " + expectClsName);
}
}
/*
* Account for database renaming. The dbClassName or dbKeyName
* will be null if the format has not yet been stored. [#16655]
*/
final String dbClassName =
catalog.getDatabaseClassName(entityClassName);
final String dbKeyName =
catalog.getDatabaseKeyName(entityClassName, keyName);
if (dbClassName != null && dbKeyName != null) {
/*
* Use a no-wait transaction to avoid blocking on a Replica
* while attempting to open an index that is currently being
* populated via the replication stream from the Master.
*/
Transaction txn = null;
if (getPrimaryConfig(entityMeta).getTransactional() &&
DbCompat.getThreadTransaction(env) == null) {
txn = env.beginTransaction(null,
autoCommitNoWaitTxnConfig);
}
boolean success = false;
try {
/*
* The doNotCreate param is true below in read-only /
* Replica upgrade mode. In this mode new secondaries are
* not available. They can be opened later when the
* upgrade is complete on the Master, by calling
* getSecondaryIndex. [#16655]
*/
secIndex = openSecondaryIndex
(txn, primaryIndex, entityClass, entityMeta,
keyClass, keyClassName, secKeyMeta, secName,
makeSecName(dbClassName, dbKeyName),
catalog.isReadOnly() /*doNotCreate*/,
null /*priOpenState*/);
success = true;
/* */
} catch (LockConflictException e) {
/* Treat this as if the database does not exist. */
/* */
} finally {
if (success) {
if (txn != null) {
txn.commit();
}
} else {
if (txn != null) {
txn.abort();
}
}
}
}
if (secIndex == null) {
throw new IndexNotAvailableException
("SecondaryIndex not yet available on this Replica, " +
"entity class: " + entityClassName + ", key name: " +
keyName);
}
}
return secIndex;
}
/**
* Opens secondary indexes for a given primary index metadata.
*/
private void openSecondaryIndexes(Transaction txn,
EntityMetadata entityMeta,
PrimaryOpenState priOpenState)
throws DatabaseException {
String entityClassName = entityMeta.getClassName();
PrimaryIndex