com.sleepycat.persist.impl.Store Maven / Gradle / Ivy
The newest version!
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.persist.impl;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
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.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.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();
/* */
dbConfig.setReplicated(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());
dbConfig.setReplicated(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
© 2015 - 2024 Weber Informatics LLC | Privacy Policy