0001: /*-
0002: * See the file LICENSE for redistribution information.
0003: *
0004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
0005: *
0006: * $Id: Store.java,v 1.20.2.8 2008/01/07 15:14:20 cwl Exp $
0007: */
0008:
0009: package com.sleepycat.persist.impl;
0010:
0011: import java.util.ArrayList;
0012: import java.util.Comparator;
0013: import java.util.HashMap;
0014: import java.util.HashSet;
0015: import java.util.IdentityHashMap;
0016: import java.util.List;
0017: import java.util.Map;
0018: import java.util.Set;
0019: import java.util.WeakHashMap;
0020:
0021: import com.sleepycat.bind.EntityBinding;
0022: import com.sleepycat.bind.tuple.StringBinding;
0023: import com.sleepycat.compat.DbCompat;
0024: import com.sleepycat.je.Cursor;
0025: import com.sleepycat.je.CursorConfig;
0026: import com.sleepycat.je.Database;
0027: import com.sleepycat.je.DatabaseConfig;
0028: import com.sleepycat.je.DatabaseEntry;
0029: import com.sleepycat.je.DatabaseException;
0030: import com.sleepycat.je.DatabaseNotFoundException;
0031: import com.sleepycat.je.Environment;
0032: import com.sleepycat.je.ForeignKeyDeleteAction;
0033: import com.sleepycat.je.LockMode;
0034: import com.sleepycat.je.OperationStatus;
0035: import com.sleepycat.je.SecondaryConfig;
0036: import com.sleepycat.je.SecondaryDatabase;
0037: import com.sleepycat.je.Sequence;
0038: import com.sleepycat.je.SequenceConfig;
0039: import com.sleepycat.je.Transaction;
0040: import com.sleepycat.persist.PrimaryIndex;
0041: import com.sleepycat.persist.SecondaryIndex;
0042: import com.sleepycat.persist.StoreConfig;
0043: import com.sleepycat.persist.evolve.Converter;
0044: import com.sleepycat.persist.evolve.EvolveConfig;
0045: import com.sleepycat.persist.evolve.EvolveEvent;
0046: import com.sleepycat.persist.evolve.EvolveInternal;
0047: import com.sleepycat.persist.evolve.EvolveListener;
0048: import com.sleepycat.persist.evolve.EvolveStats;
0049: import com.sleepycat.persist.evolve.Mutations;
0050: import com.sleepycat.persist.model.ClassMetadata;
0051: import com.sleepycat.persist.model.DeleteAction;
0052: import com.sleepycat.persist.model.EntityMetadata;
0053: import com.sleepycat.persist.model.EntityModel;
0054: import com.sleepycat.persist.model.FieldMetadata;
0055: import com.sleepycat.persist.model.ModelInternal;
0056: import com.sleepycat.persist.model.PrimaryKeyMetadata;
0057: import com.sleepycat.persist.model.Relationship;
0058: import com.sleepycat.persist.model.SecondaryKeyMetadata;
0059: import com.sleepycat.persist.raw.RawObject;
0060:
0061: /**
0062: * Base implementation for EntityStore and RawStore. The methods here
0063: * correspond directly to those in EntityStore; see EntityStore documentation
0064: * for details.
0065: *
0066: * @author Mark Hayes
0067: */
0068: public class Store {
0069:
0070: private static final char NAME_SEPARATOR = '#';
0071: private static final String NAME_PREFIX = "persist"
0072: + NAME_SEPARATOR;
0073: private static final String DB_NAME_PREFIX = "com.sleepycat.persist.";
0074: private static final String CATALOG_DB = DB_NAME_PREFIX + "formats";
0075: private static final String SEQUENCE_DB = DB_NAME_PREFIX
0076: + "sequences";
0077:
0078: private static Map<Environment, Map<String, PersistCatalog>> catalogPool = new WeakHashMap<Environment, Map<String, PersistCatalog>>();
0079:
0080: /* For unit testing. */
0081: private static SyncHook syncHook;
0082:
0083: private Environment env;
0084: private boolean rawAccess;
0085: private PersistCatalog catalog;
0086: private EntityModel model;
0087: private Mutations mutations;
0088: private StoreConfig storeConfig;
0089: private String storeName;
0090: private String storePrefix;
0091: private Map<String, PrimaryIndex> priIndexMap;
0092: private Map<String, SecondaryIndex> secIndexMap;
0093: private Map<String, DatabaseConfig> priConfigMap;
0094: private Map<String, SecondaryConfig> secConfigMap;
0095: private Map<String, PersistKeyBinding> keyBindingMap;
0096: private Map<String, Sequence> sequenceMap;
0097: private Map<String, SequenceConfig> sequenceConfigMap;
0098: private Database sequenceDb;
0099: private IdentityHashMap<Database, Object> deferredWriteDatabases;
0100: private Map<String, Set<String>> inverseRelatedEntityMap;
0101:
0102: public Store(Environment env, String storeName, StoreConfig config,
0103: boolean rawAccess) throws DatabaseException {
0104:
0105: this .env = env;
0106: this .storeName = storeName;
0107: this .rawAccess = rawAccess;
0108:
0109: if (env == null || storeName == null) {
0110: throw new NullPointerException(
0111: "env and storeName parameters must not be null");
0112: }
0113: if (config != null) {
0114: model = config.getModel();
0115: mutations = config.getMutations();
0116: }
0117: if (config == null) {
0118: storeConfig = StoreConfig.DEFAULT;
0119: } else {
0120: storeConfig = config.cloneConfig();
0121: }
0122:
0123: storePrefix = NAME_PREFIX + storeName + NAME_SEPARATOR;
0124: priIndexMap = new HashMap<String, PrimaryIndex>();
0125: secIndexMap = new HashMap<String, SecondaryIndex>();
0126: priConfigMap = new HashMap<String, DatabaseConfig>();
0127: secConfigMap = new HashMap<String, SecondaryConfig>();
0128: keyBindingMap = new HashMap<String, PersistKeyBinding>();
0129: sequenceMap = new HashMap<String, Sequence>();
0130: sequenceConfigMap = new HashMap<String, SequenceConfig>();
0131: deferredWriteDatabases = new IdentityHashMap<Database, Object>();
0132:
0133: if (rawAccess) {
0134: /* Open a read-only catalog that uses the stored model. */
0135: if (model != null) {
0136: throw new IllegalArgumentException(
0137: "A model may not be specified when opening a RawStore");
0138: }
0139: DatabaseConfig dbConfig = new DatabaseConfig();
0140: dbConfig.setReadOnly(true);
0141: dbConfig.setTransactional(storeConfig.getTransactional());
0142: catalog = new PersistCatalog(null, env, storePrefix,
0143: storePrefix + CATALOG_DB, dbConfig, model,
0144: mutations, rawAccess, this );
0145: } else {
0146: /* Open the shared catalog that uses the current model. */
0147: synchronized (catalogPool) {
0148: Map<String, PersistCatalog> catalogMap = catalogPool
0149: .get(env);
0150: if (catalogMap == null) {
0151: catalogMap = new HashMap<String, PersistCatalog>();
0152: catalogPool.put(env, catalogMap);
0153: }
0154: catalog = catalogMap.get(storeName);
0155: if (catalog != null) {
0156: catalog.openExisting();
0157: } else {
0158: Transaction txn = null;
0159: if (storeConfig.getTransactional()
0160: && env.getThreadTransaction() == null) {
0161: txn = env.beginTransaction(null, null);
0162: }
0163: boolean success = false;
0164: try {
0165: DatabaseConfig dbConfig = new DatabaseConfig();
0166: dbConfig.setAllowCreate(storeConfig
0167: .getAllowCreate());
0168: dbConfig.setReadOnly(storeConfig.getReadOnly());
0169: dbConfig.setTransactional(storeConfig
0170: .getTransactional());
0171: catalog = new PersistCatalog(txn, env,
0172: storePrefix, storePrefix + CATALOG_DB,
0173: dbConfig, model, mutations, rawAccess,
0174: this );
0175: catalogMap.put(storeName, catalog);
0176: success = true;
0177: } finally {
0178: if (txn != null) {
0179: if (success) {
0180: txn.commit();
0181: } else {
0182: txn.abort();
0183: }
0184: }
0185: }
0186: }
0187: }
0188: }
0189:
0190: /* Get the merged mutations from the catalog. */
0191: mutations = catalog.getMutations();
0192:
0193: /*
0194: * If there is no model parameter, use the default or stored model
0195: * obtained from the catalog.
0196: */
0197: model = catalog.getResolvedModel();
0198:
0199: /*
0200: * Give the model a reference to the catalog to fully initialize the
0201: * model. Only then may we initialize the Converter mutations, which
0202: * themselves may call model methods and expect the model to be fully
0203: * initialized.
0204: */
0205: ModelInternal.setCatalog(model, catalog);
0206: for (Converter converter : mutations.getConverters()) {
0207: converter.getConversion().initialize(model);
0208: }
0209:
0210: /*
0211: * For each existing entity with a relatedEntity reference, create an
0212: * inverse map (back pointer) from the class named in the relatedEntity
0213: * to the class containing the secondary key. This is used to open the
0214: * class containing the secondary key whenever we open the
0215: * relatedEntity class, to configure foreign key constraints. Note that
0216: * we do not need to update this map as new primary indexes are
0217: * created, because opening the new index will setup the foreign key
0218: * constraints. [#15358]
0219: */
0220: inverseRelatedEntityMap = new HashMap<String, Set<String>>();
0221: List<Format> entityFormats = new ArrayList<Format>();
0222: catalog.getEntityFormats(entityFormats);
0223: for (Format entityFormat : entityFormats) {
0224: EntityMetadata entityMeta = entityFormat
0225: .getEntityMetadata();
0226: for (SecondaryKeyMetadata secKeyMeta : entityMeta
0227: .getSecondaryKeys().values()) {
0228: String relatedClsName = secKeyMeta.getRelatedEntity();
0229: if (relatedClsName != null) {
0230: Set<String> inverseClassNames = inverseRelatedEntityMap
0231: .get(relatedClsName);
0232: if (inverseClassNames == null) {
0233: inverseClassNames = new HashSet<String>();
0234: inverseRelatedEntityMap.put(relatedClsName,
0235: inverseClassNames);
0236: }
0237: inverseClassNames.add(entityMeta.getClassName());
0238: }
0239: }
0240: }
0241: }
0242:
0243: public Environment getEnvironment() {
0244: return env;
0245: }
0246:
0247: public StoreConfig getConfig() {
0248: return storeConfig.cloneConfig();
0249: }
0250:
0251: public String getStoreName() {
0252: return storeName;
0253: }
0254:
0255: public void dumpCatalog() {
0256: catalog.dump();
0257: }
0258:
0259: public static Set<String> getStoreNames(Environment env)
0260: throws DatabaseException {
0261:
0262: Set<String> set = new HashSet<String>();
0263: for (Object o : env.getDatabaseNames()) {
0264: String s = (String) o;
0265: if (s.startsWith(NAME_PREFIX)) {
0266: int start = NAME_PREFIX.length();
0267: int end = s.indexOf(NAME_SEPARATOR, start);
0268: set.add(s.substring(start, end));
0269: }
0270: }
0271: return set;
0272: }
0273:
0274: public EntityModel getModel() {
0275: return model;
0276: }
0277:
0278: public Mutations getMutations() {
0279: return mutations;
0280: }
0281:
0282: /**
0283: * A getPrimaryIndex with extra parameters for opening a raw store.
0284: * primaryKeyClass and entityClass are used for generic typing; for a raw
0285: * store, these should always be Object.class and RawObject.class.
0286: * primaryKeyClassName is used for consistency checking and should be null
0287: * for a raw store only. entityClassName is used to identify the store and
0288: * may not be null.
0289: */
0290: public synchronized <PK, E> PrimaryIndex<PK, E> getPrimaryIndex(
0291: Class<PK> primaryKeyClass, String primaryKeyClassName,
0292: Class<E> entityClass, String entityClassName)
0293: throws DatabaseException {
0294:
0295: assert (rawAccess && entityClass == RawObject.class)
0296: || (!rawAccess && entityClass != RawObject.class);
0297: assert (rawAccess && primaryKeyClassName == null)
0298: || (!rawAccess && primaryKeyClassName != null);
0299:
0300: checkOpen();
0301:
0302: PrimaryIndex<PK, E> priIndex = priIndexMap.get(entityClassName);
0303: if (priIndex == null) {
0304:
0305: /* Check metadata. */
0306: EntityMetadata entityMeta = checkEntityClass(entityClassName);
0307: PrimaryKeyMetadata priKeyMeta = entityMeta.getPrimaryKey();
0308: if (primaryKeyClassName == null) {
0309: primaryKeyClassName = priKeyMeta.getClassName();
0310: } else {
0311: String expectClsName = SimpleCatalog
0312: .keyClassName(priKeyMeta.getClassName());
0313: if (!primaryKeyClassName.equals(expectClsName)) {
0314: throw new IllegalArgumentException(
0315: "Wrong primary key class: "
0316: + primaryKeyClassName
0317: + " Correct class is: "
0318: + expectClsName);
0319: }
0320: }
0321:
0322: /* Create bindings. */
0323: PersistEntityBinding entityBinding = new PersistEntityBinding(
0324: catalog, entityClassName, rawAccess);
0325: PersistKeyBinding keyBinding = getKeyBinding(primaryKeyClassName);
0326:
0327: /* If not read-only, get the primary key sequence. */
0328: String seqName = priKeyMeta.getSequenceName();
0329: if (!storeConfig.getReadOnly() && seqName != null) {
0330: entityBinding.keyAssigner = new PersistKeyAssigner(
0331: keyBinding, entityBinding, getSequence(seqName));
0332: }
0333:
0334: /*
0335: * Use a single transaction for opening the primary DB and its
0336: * secondaries. If opening any secondary fails, abort the
0337: * transaction and undo the changes to the state of the store.
0338: * Also support undo if the store is non-transactional.
0339: */
0340: Transaction txn = null;
0341: DatabaseConfig dbConfig = getPrimaryConfig(entityMeta);
0342: if (dbConfig.getTransactional()
0343: && env.getThreadTransaction() == null) {
0344: txn = env.beginTransaction(null, null);
0345: }
0346: PrimaryOpenState priOpenState = new PrimaryOpenState(
0347: entityClassName);
0348: boolean success = false;
0349: try {
0350:
0351: /* Open the primary database. */
0352: String dbName = storePrefix + entityClassName;
0353: Database db = env.openDatabase(txn, dbName, dbConfig);
0354: priOpenState.addDatabase(db);
0355:
0356: /* Create index object. */
0357: priIndex = new PrimaryIndex(db, primaryKeyClass,
0358: keyBinding, entityClass, entityBinding);
0359:
0360: /* Update index and database maps. */
0361: priIndexMap.put(entityClassName, priIndex);
0362: if (DbCompat.getDeferredWrite(dbConfig)) {
0363: deferredWriteDatabases.put(db, null);
0364: }
0365:
0366: /* If not read-only, open all associated secondaries. */
0367: if (!dbConfig.getReadOnly()) {
0368: openSecondaryIndexes(txn, entityMeta, priOpenState);
0369:
0370: /*
0371: * To enable foreign key contratints, also open all primary
0372: * indexes referring to this class via a relatedEntity
0373: * property in another entity. [#15358]
0374: */
0375: Set<String> inverseClassNames = inverseRelatedEntityMap
0376: .get(entityClassName);
0377: if (inverseClassNames != null) {
0378: for (String relatedClsName : inverseClassNames) {
0379: getRelatedIndex(relatedClsName);
0380: }
0381: }
0382: }
0383: success = true;
0384: } finally {
0385: if (success) {
0386: if (txn != null) {
0387: txn.commit();
0388: }
0389: } else {
0390: if (txn != null) {
0391: txn.abort();
0392: } else {
0393: priOpenState.closeDatabases();
0394: }
0395: priOpenState.undoState();
0396: }
0397: }
0398: }
0399: return priIndex;
0400: }
0401:
0402: /**
0403: * Holds state information about opening a primary index and its secondary
0404: * indexes. Used to undo the state of this object if the transaction
0405: * opening the primary and secondaries aborts. Also used to close all
0406: * databases opened during this process for a non-transactional store.
0407: */
0408: private class PrimaryOpenState {
0409:
0410: private String entityClassName;
0411: private IdentityHashMap<Database, Object> databases;
0412: private Set<String> secNames;
0413:
0414: PrimaryOpenState(String entityClassName) {
0415: this .entityClassName = entityClassName;
0416: databases = new IdentityHashMap<Database, Object>();
0417: secNames = new HashSet<String>();
0418: }
0419:
0420: /**
0421: * Save a database that was opening during this operation.
0422: */
0423: void addDatabase(Database db) {
0424: databases.put(db, null);
0425: }
0426:
0427: /**
0428: * Save the name of a secondary index that was opening during this
0429: * operation.
0430: */
0431: void addSecondaryName(String secName) {
0432: secNames.add(secName);
0433: }
0434:
0435: /**
0436: * Close any databases opened during this operation when it fails.
0437: * This method should be called if a non-transactional operation fails,
0438: * since we cannot rely on the transaction abort to cleanup any
0439: * databases that were opened.
0440: */
0441: void closeDatabases() {
0442: for (Database db : databases.keySet()) {
0443: try {
0444: db.close();
0445: } catch (Exception ignored) {
0446: }
0447: }
0448: }
0449:
0450: /**
0451: * Reset all state information when this operation fails. This method
0452: * should be called for both transactional and non-transsactional
0453: * operation.
0454: */
0455: void undoState() {
0456: priIndexMap.remove(entityClassName);
0457: for (String secName : secNames) {
0458: secIndexMap.remove(secName);
0459: }
0460: for (Database db : databases.keySet()) {
0461: deferredWriteDatabases.remove(db);
0462: }
0463: }
0464: }
0465:
0466: /**
0467: * Opens a primary index related via a foreign key (relatedEntity).
0468: * Related indexes are not opened in the same transaction used by the
0469: * caller to open a primary or secondary. It is OK to leave the related
0470: * index open when the caller's transaction aborts. It is only important
0471: * to open a primary and its secondaries atomically.
0472: */
0473: private PrimaryIndex getRelatedIndex(String relatedClsName)
0474: throws DatabaseException {
0475:
0476: PrimaryIndex relatedIndex = priIndexMap.get(relatedClsName);
0477: if (relatedIndex == null) {
0478: EntityMetadata relatedEntityMeta = checkEntityClass(relatedClsName);
0479: Class relatedKeyCls;
0480: String relatedKeyClsName;
0481: Class relatedCls;
0482: if (rawAccess) {
0483: relatedCls = RawObject.class;
0484: relatedKeyCls = Object.class;
0485: relatedKeyClsName = null;
0486: } else {
0487: try {
0488: relatedCls = EntityModel
0489: .classForName(relatedClsName);
0490: } catch (ClassNotFoundException e) {
0491: throw new IllegalArgumentException(
0492: "Related entity class not found: "
0493: + relatedClsName);
0494: }
0495: relatedKeyClsName = SimpleCatalog
0496: .keyClassName(relatedEntityMeta.getPrimaryKey()
0497: .getClassName());
0498: relatedKeyCls = SimpleCatalog
0499: .keyClassForName(relatedKeyClsName);
0500: }
0501:
0502: /*
0503: * Cycles are prevented here by adding primary indexes to the
0504: * priIndexMap as soon as they are created, before opening related
0505: * indexes.
0506: */
0507: relatedIndex = getPrimaryIndex(relatedKeyCls,
0508: relatedKeyClsName, relatedCls, relatedClsName);
0509: }
0510: return relatedIndex;
0511: }
0512:
0513: /**
0514: * A getSecondaryIndex with extra parameters for opening a raw store.
0515: * keyClassName is used for consistency checking and should be null for a
0516: * raw store only.
0517: */
0518: public synchronized <SK, PK, E1, E2 extends E1> SecondaryIndex<SK, PK, E2> getSecondaryIndex(
0519: PrimaryIndex<PK, E1> primaryIndex, Class<E2> entityClass,
0520: String entityClassName, Class<SK> keyClass,
0521: String keyClassName, String keyName)
0522: throws DatabaseException {
0523:
0524: assert (rawAccess && keyClassName == null)
0525: || (!rawAccess && keyClassName != null);
0526:
0527: checkOpen();
0528:
0529: EntityMetadata entityMeta = null;
0530: SecondaryKeyMetadata secKeyMeta = null;
0531:
0532: /* Validate the subclass for a subclass index. */
0533: if (entityClass != primaryIndex.getEntityClass()) {
0534: entityMeta = model.getEntityMetadata(entityClassName);
0535: assert entityMeta != null;
0536: secKeyMeta = checkSecKey(entityMeta, keyName);
0537: String subclassName = entityClass.getName();
0538: String declaringClassName = secKeyMeta
0539: .getDeclaringClassName();
0540: if (!subclassName.equals(declaringClassName)) {
0541: throw new IllegalArgumentException("Key for subclass "
0542: + subclassName
0543: + " is declared in a different class: "
0544: + makeSecName(declaringClassName, keyName));
0545: }
0546: }
0547:
0548: /*
0549: * Even though the primary is already open, we can't assume the
0550: * secondary is open because we don't automatically open all
0551: * secondaries when the primary is read-only. Use auto-commit (a null
0552: * transaction) since we're opening only one database.
0553: */
0554: String secName = makeSecName(entityClassName, keyName);
0555: SecondaryIndex<SK, PK, E2> secIndex = secIndexMap.get(secName);
0556: if (secIndex == null) {
0557: if (entityMeta == null) {
0558: entityMeta = model.getEntityMetadata(entityClassName);
0559: assert entityMeta != null;
0560: }
0561: if (secKeyMeta == null) {
0562: secKeyMeta = checkSecKey(entityMeta, keyName);
0563: }
0564:
0565: /* Check metadata. */
0566: if (keyClassName == null) {
0567: keyClassName = getSecKeyClass(secKeyMeta);
0568: } else {
0569: String expectClsName = getSecKeyClass(secKeyMeta);
0570: if (!keyClassName.equals(expectClsName)) {
0571: throw new IllegalArgumentException(
0572: "Wrong secondary key class: "
0573: + keyClassName
0574: + " Correct class is: "
0575: + expectClsName);
0576: }
0577: }
0578:
0579: secIndex = openSecondaryIndex(null, primaryIndex,
0580: entityClass, entityMeta, keyClass, keyClassName,
0581: secKeyMeta, secName, false /*doNotCreate*/, null /*priOpenState*/);
0582: }
0583: return secIndex;
0584: }
0585:
0586: /**
0587: * Opens any secondary indexes defined in the given entity metadata that
0588: * are not already open. This method is called when a new entity subclass
0589: * is encountered when an instance of that class is stored, and the
0590: * EntityStore.getSubclassIndex has not been previously called for that
0591: * class. [#15247]
0592: */
0593: synchronized void openSecondaryIndexes(Transaction txn,
0594: EntityMetadata entityMeta, PrimaryOpenState priOpenState)
0595: throws DatabaseException {
0596:
0597: String entityClassName = entityMeta.getClassName();
0598: PrimaryIndex<Object, Object> priIndex = priIndexMap
0599: .get(entityClassName);
0600: assert priIndex != null;
0601: Class<Object> entityClass = priIndex.getEntityClass();
0602:
0603: for (SecondaryKeyMetadata secKeyMeta : entityMeta
0604: .getSecondaryKeys().values()) {
0605: String keyName = secKeyMeta.getKeyName();
0606: String secName = makeSecName(entityClassName, keyName);
0607: SecondaryIndex<Object, Object, Object> secIndex = secIndexMap
0608: .get(secName);
0609: if (secIndex == null) {
0610: String keyClassName = getSecKeyClass(secKeyMeta);
0611: /* RawMode: should not require class. */
0612: Class keyClass = SimpleCatalog
0613: .keyClassForName(keyClassName);
0614: openSecondaryIndex(
0615: txn,
0616: priIndex,
0617: entityClass,
0618: entityMeta,
0619: keyClass,
0620: keyClassName,
0621: secKeyMeta,
0622: makeSecName(entityClassName, secKeyMeta
0623: .getKeyName()),
0624: storeConfig.getSecondaryBulkLoad() /*doNotCreate*/,
0625: priOpenState);
0626: }
0627: }
0628: }
0629:
0630: /**
0631: * Opens a secondary index with a given transaction and adds it to the
0632: * secIndexMap. We assume that the index is not already open.
0633: */
0634: private <SK, PK, E1, E2 extends E1> SecondaryIndex<SK, PK, E2> openSecondaryIndex(
0635: Transaction txn, PrimaryIndex<PK, E1> primaryIndex,
0636: Class<E2> entityClass, EntityMetadata entityMeta,
0637: Class<SK> keyClass, String keyClassName,
0638: SecondaryKeyMetadata secKeyMeta, String secName,
0639: boolean doNotCreate, PrimaryOpenState priOpenState)
0640: throws DatabaseException {
0641:
0642: assert !secIndexMap.containsKey(secName);
0643: String dbName = storePrefix + secName;
0644: SecondaryConfig config = getSecondaryConfig(secName,
0645: entityMeta, keyClassName, secKeyMeta);
0646: Database priDb = primaryIndex.getDatabase();
0647: DatabaseConfig priConfig = priDb.getConfig();
0648:
0649: String relatedClsName = secKeyMeta.getRelatedEntity();
0650: if (relatedClsName != null) {
0651: PrimaryIndex relatedIndex = getRelatedIndex(relatedClsName);
0652: config.setForeignKeyDatabase(relatedIndex.getDatabase());
0653: }
0654:
0655: if (config.getTransactional() != priConfig.getTransactional()
0656: || DbCompat.getDeferredWrite(config) != DbCompat
0657: .getDeferredWrite(priConfig)
0658: || config.getReadOnly() != priConfig.getReadOnly()) {
0659: throw new IllegalArgumentException(
0660: "One of these properties was changed to be inconsistent"
0661: + " with the associated primary database: "
0662: + " Transactional, DeferredWrite, ReadOnly");
0663: }
0664:
0665: PersistKeyBinding keyBinding = getKeyBinding(keyClassName);
0666:
0667: /*
0668: * doNotCreate is true when StoreConfig.getSecondaryBulkLoad is true
0669: * and we are opening a secondary as a side effect of opening a
0670: * primary, i.e., getSecondaryIndex is not being called. If
0671: * doNotCreate is true and the database does not exist, we silently
0672: * ignore the DatabaseNotFoundException and return null. When
0673: * getSecondaryIndex is subsequently called, the secondary database
0674: * will be created and populated from the primary -- a bulk load.
0675: */
0676: SecondaryDatabase db;
0677: boolean saveAllowCreate = config.getAllowCreate();
0678: try {
0679: if (doNotCreate) {
0680: config.setAllowCreate(false);
0681: }
0682: db = env.openSecondaryDatabase(txn, dbName, priDb, config);
0683: } catch (DatabaseNotFoundException e) {
0684: if (doNotCreate) {
0685: return null;
0686: } else {
0687: throw e;
0688: }
0689: } finally {
0690: if (doNotCreate) {
0691: config.setAllowCreate(saveAllowCreate);
0692: }
0693: }
0694: SecondaryIndex<SK, PK, E2> secIndex = new SecondaryIndex(db,
0695: null, primaryIndex, keyClass, keyBinding);
0696:
0697: /* Update index and database maps. */
0698: secIndexMap.put(secName, secIndex);
0699: if (DbCompat.getDeferredWrite(config)) {
0700: deferredWriteDatabases.put(db, null);
0701: }
0702: if (priOpenState != null) {
0703: priOpenState.addDatabase(db);
0704: priOpenState.addSecondaryName(secName);
0705: }
0706: return secIndex;
0707: }
0708:
0709: public void sync() throws DatabaseException {
0710:
0711: List<Database> dbs = new ArrayList<Database>();
0712: synchronized (this ) {
0713: dbs.addAll(deferredWriteDatabases.keySet());
0714: }
0715: int nDbs = dbs.size();
0716: if (nDbs > 0) {
0717: for (int i = 0; i < nDbs; i += 1) {
0718: Database db = dbs.get(i);
0719: boolean flushLog = (i == nDbs - 1);
0720: DbCompat.syncDeferredWrite(db, flushLog);
0721: /* Call hook for unit testing. */
0722: if (syncHook != null) {
0723: syncHook.onSync(db, flushLog);
0724: }
0725: }
0726: }
0727: }
0728:
0729: public void truncateClass(Class entityClass)
0730: throws DatabaseException {
0731:
0732: truncateClass(null, entityClass);
0733: }
0734:
0735: public synchronized void truncateClass(Transaction txn,
0736: Class entityClass) throws DatabaseException {
0737:
0738: checkOpen();
0739:
0740: /* Close primary and secondary databases. */
0741: closeClass(entityClass);
0742:
0743: String clsName = entityClass.getName();
0744: EntityMetadata entityMeta = checkEntityClass(clsName);
0745:
0746: /*
0747: * Truncate the primary first and let any exceptions propogate
0748: * upwards. Then truncate each secondary, only throwing the first
0749: * exception.
0750: */
0751: String dbName = storePrefix + clsName;
0752: boolean primaryExists = true;
0753: try {
0754: env.truncateDatabase(txn, dbName, false);
0755: } catch (DatabaseNotFoundException ignored) {
0756: primaryExists = false;
0757: }
0758: if (primaryExists) {
0759: DatabaseException firstException = null;
0760: for (SecondaryKeyMetadata keyMeta : entityMeta
0761: .getSecondaryKeys().values()) {
0762: try {
0763: env.truncateDatabase(txn,
0764: storePrefix
0765: + makeSecName(clsName, keyMeta
0766: .getKeyName()), false);
0767: } catch (DatabaseNotFoundException ignored) {
0768: /* Ignore secondaries that do not exist. */
0769: } catch (DatabaseException e) {
0770: if (firstException == null) {
0771: firstException = e;
0772: }
0773: }
0774: }
0775: if (firstException != null) {
0776: throw firstException;
0777: }
0778: }
0779: }
0780:
0781: public synchronized void closeClass(Class entityClass)
0782: throws DatabaseException {
0783:
0784: checkOpen();
0785: String clsName = entityClass.getName();
0786: EntityMetadata entityMeta = checkEntityClass(clsName);
0787:
0788: PrimaryIndex priIndex = priIndexMap.get(clsName);
0789: if (priIndex != null) {
0790: /* Close the secondaries first. */
0791: DatabaseException firstException = null;
0792: for (SecondaryKeyMetadata keyMeta : entityMeta
0793: .getSecondaryKeys().values()) {
0794:
0795: String secName = makeSecName(clsName, keyMeta
0796: .getKeyName());
0797: SecondaryIndex secIndex = secIndexMap.get(secName);
0798: if (secIndex != null) {
0799: Database db = secIndex.getDatabase();
0800: firstException = closeDb(db, firstException);
0801: firstException = closeDb(
0802: secIndex.getKeysDatabase(), firstException);
0803: secIndexMap.remove(secName);
0804: deferredWriteDatabases.remove(db);
0805: }
0806: }
0807: /* Close the primary last. */
0808: Database db = priIndex.getDatabase();
0809: firstException = closeDb(db, firstException);
0810: priIndexMap.remove(clsName);
0811: deferredWriteDatabases.remove(db);
0812:
0813: /* Throw the first exception encountered. */
0814: if (firstException != null) {
0815: throw firstException;
0816: }
0817: }
0818: }
0819:
0820: public synchronized void close() throws DatabaseException {
0821:
0822: checkOpen();
0823: DatabaseException firstException = null;
0824: try {
0825: if (rawAccess) {
0826: boolean allClosed = catalog.close();
0827: assert allClosed;
0828: } else {
0829: synchronized (catalogPool) {
0830: Map<String, PersistCatalog> catalogMap = catalogPool
0831: .get(env);
0832: assert catalogMap != null;
0833: if (catalog.close()) {
0834: /* Remove when the reference count goes to zero. */
0835: catalogMap.remove(storeName);
0836: }
0837: }
0838: }
0839: catalog = null;
0840: } catch (DatabaseException e) {
0841: if (firstException == null) {
0842: firstException = e;
0843: }
0844: }
0845: firstException = closeDb(sequenceDb, firstException);
0846: for (SecondaryIndex index : secIndexMap.values()) {
0847: firstException = closeDb(index.getDatabase(),
0848: firstException);
0849: firstException = closeDb(index.getKeysDatabase(),
0850: firstException);
0851: }
0852: for (PrimaryIndex index : priIndexMap.values()) {
0853: firstException = closeDb(index.getDatabase(),
0854: firstException);
0855: }
0856: if (firstException != null) {
0857: throw firstException;
0858: }
0859: }
0860:
0861: public synchronized Sequence getSequence(String name)
0862: throws DatabaseException {
0863:
0864: checkOpen();
0865:
0866: if (storeConfig.getReadOnly()) {
0867: throw new IllegalStateException("Store is read-only");
0868: }
0869:
0870: Sequence seq = sequenceMap.get(name);
0871: if (seq == null) {
0872: if (sequenceDb == null) {
0873: String dbName = storePrefix + SEQUENCE_DB;
0874: DatabaseConfig dbConfig = new DatabaseConfig();
0875: dbConfig.setTransactional(storeConfig
0876: .getTransactional());
0877: dbConfig.setAllowCreate(true);
0878: sequenceDb = env.openDatabase(null, dbName, dbConfig);
0879: }
0880: DatabaseEntry entry = new DatabaseEntry();
0881: StringBinding.stringToEntry(name, entry);
0882: seq = sequenceDb.openSequence(null, entry,
0883: getSequenceConfig(name));
0884: sequenceMap.put(name, seq);
0885: }
0886: return seq;
0887: }
0888:
0889: public synchronized SequenceConfig getSequenceConfig(String name) {
0890: checkOpen();
0891: SequenceConfig config = sequenceConfigMap.get(name);
0892: if (config == null) {
0893: config = new SequenceConfig();
0894: config.setInitialValue(1);
0895: config.setRange(1, Long.MAX_VALUE);
0896: config.setCacheSize(100);
0897: config.setAutoCommitNoSync(true);
0898: config.setAllowCreate(!storeConfig.getReadOnly());
0899: sequenceConfigMap.put(name, config);
0900: }
0901: return config;
0902: }
0903:
0904: public synchronized void setSequenceConfig(String name,
0905: SequenceConfig config) {
0906: checkOpen();
0907: sequenceConfigMap.put(name, config);
0908: }
0909:
0910: public synchronized DatabaseConfig getPrimaryConfig(
0911: Class entityClass) {
0912: checkOpen();
0913: String clsName = entityClass.getName();
0914: EntityMetadata meta = checkEntityClass(clsName);
0915: return getPrimaryConfig(meta).cloneConfig();
0916: }
0917:
0918: private synchronized DatabaseConfig getPrimaryConfig(
0919: EntityMetadata meta) {
0920: String clsName = meta.getClassName();
0921: DatabaseConfig config = priConfigMap.get(clsName);
0922: if (config == null) {
0923: config = new DatabaseConfig();
0924: config.setTransactional(storeConfig.getTransactional());
0925: config.setAllowCreate(!storeConfig.getReadOnly());
0926: config.setReadOnly(storeConfig.getReadOnly());
0927: DbCompat.setDeferredWrite(config, storeConfig
0928: .getDeferredWrite());
0929: setBtreeComparator(config, meta.getPrimaryKey()
0930: .getClassName());
0931: priConfigMap.put(clsName, config);
0932: }
0933: return config;
0934: }
0935:
0936: public synchronized void setPrimaryConfig(Class entityClass,
0937: DatabaseConfig config) {
0938: checkOpen();
0939: String clsName = entityClass.getName();
0940: if (priIndexMap.containsKey(clsName)) {
0941: throw new IllegalStateException(
0942: "Cannot set config after DB is open");
0943: }
0944: EntityMetadata meta = checkEntityClass(clsName);
0945: DatabaseConfig dbConfig = getPrimaryConfig(meta);
0946: if (config.getSortedDuplicates()
0947: || config.getBtreeComparator() != dbConfig
0948: .getBtreeComparator()) {
0949: throw new IllegalArgumentException(
0950: "One of these properties was illegally changed: "
0951: + " SortedDuplicates or BtreeComparator");
0952: }
0953: priConfigMap.put(clsName, config);
0954: }
0955:
0956: public synchronized SecondaryConfig getSecondaryConfig(
0957: Class entityClass, String keyName) {
0958: checkOpen();
0959: String entityClsName = entityClass.getName();
0960: EntityMetadata entityMeta = checkEntityClass(entityClsName);
0961: SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta,
0962: keyName);
0963: String keyClassName = getSecKeyClass(secKeyMeta);
0964: String secName = makeSecName(entityClass.getName(), keyName);
0965: return (SecondaryConfig) getSecondaryConfig(secName,
0966: entityMeta, keyClassName, secKeyMeta).cloneConfig();
0967: }
0968:
0969: private SecondaryConfig getSecondaryConfig(String secName,
0970: EntityMetadata entityMeta, String keyClassName,
0971: SecondaryKeyMetadata secKeyMeta) {
0972: SecondaryConfig config = secConfigMap.get(secName);
0973: if (config == null) {
0974: /* Set common properties to match the primary DB. */
0975: DatabaseConfig priConfig = getPrimaryConfig(entityMeta);
0976: config = new SecondaryConfig();
0977: config.setTransactional(priConfig.getTransactional());
0978: config.setAllowCreate(!priConfig.getReadOnly());
0979: config.setReadOnly(priConfig.getReadOnly());
0980: DbCompat.setDeferredWrite(config, DbCompat
0981: .getDeferredWrite(priConfig));
0982: /* Set secondary properties based on metadata. */
0983: config.setAllowPopulate(true);
0984: Relationship rel = secKeyMeta.getRelationship();
0985: config.setSortedDuplicates(rel == Relationship.MANY_TO_ONE
0986: || rel == Relationship.MANY_TO_MANY);
0987: setBtreeComparator(config, secKeyMeta.getClassName());
0988: PersistKeyCreator keyCreator = new PersistKeyCreator(
0989: catalog, entityMeta, keyClassName, secKeyMeta);
0990: if (rel == Relationship.ONE_TO_MANY
0991: || rel == Relationship.MANY_TO_MANY) {
0992: config.setMultiKeyCreator(keyCreator);
0993: } else {
0994: config.setKeyCreator(keyCreator);
0995: }
0996: DeleteAction deleteAction = secKeyMeta.getDeleteAction();
0997: if (deleteAction != null) {
0998: ForeignKeyDeleteAction baseDeleteAction;
0999: switch (deleteAction) {
1000: case ABORT:
1001: baseDeleteAction = ForeignKeyDeleteAction.ABORT;
1002: break;
1003: case CASCADE:
1004: baseDeleteAction = ForeignKeyDeleteAction.CASCADE;
1005: break;
1006: case NULLIFY:
1007: baseDeleteAction = ForeignKeyDeleteAction.NULLIFY;
1008: break;
1009: default:
1010: throw new IllegalStateException(deleteAction
1011: .toString());
1012: }
1013: config.setForeignKeyDeleteAction(baseDeleteAction);
1014: if (deleteAction == DeleteAction.NULLIFY) {
1015: config.setForeignMultiKeyNullifier(keyCreator);
1016: }
1017: }
1018: secConfigMap.put(secName, config);
1019: }
1020: return config;
1021: }
1022:
1023: public synchronized void setSecondaryConfig(Class entityClass,
1024: String keyName, SecondaryConfig config) {
1025: checkOpen();
1026: String entityClsName = entityClass.getName();
1027: EntityMetadata entityMeta = checkEntityClass(entityClsName);
1028: SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta,
1029: keyName);
1030: String keyClassName = getSecKeyClass(secKeyMeta);
1031: String secName = makeSecName(entityClass.getName(), keyName);
1032: if (secIndexMap.containsKey(secName)) {
1033: throw new IllegalStateException(
1034: "Cannot set config after DB is open");
1035: }
1036: SecondaryConfig dbConfig = getSecondaryConfig(secName,
1037: entityMeta, keyClassName, secKeyMeta);
1038: if (config.getSortedDuplicates() != dbConfig
1039: .getSortedDuplicates()
1040: || config.getBtreeComparator() != dbConfig
1041: .getBtreeComparator()
1042: || config.getDuplicateComparator() != null
1043: || config.getAllowPopulate() != dbConfig
1044: .getAllowPopulate()
1045: || config.getKeyCreator() != dbConfig.getKeyCreator()
1046: || config.getMultiKeyCreator() != dbConfig
1047: .getMultiKeyCreator()
1048: || config.getForeignKeyNullifier() != dbConfig
1049: .getForeignKeyNullifier()
1050: || config.getForeignMultiKeyNullifier() != dbConfig
1051: .getForeignMultiKeyNullifier()
1052: || config.getForeignKeyDeleteAction() != dbConfig
1053: .getForeignKeyDeleteAction()
1054: || config.getForeignKeyDatabase() != null) {
1055: throw new IllegalArgumentException(
1056: "One of these properties was illegally changed: "
1057: + " SortedDuplicates, BtreeComparator, DuplicateComparator,"
1058: + " AllowPopulate, KeyCreator, MultiKeyCreator,"
1059: + " ForeignKeyNullifer, ForeignMultiKeyNullifier,"
1060: + " ForeignKeyDeleteAction, ForeignKeyDatabase");
1061: }
1062: secConfigMap.put(secName, config);
1063: }
1064:
1065: private static String makeSecName(String entityClsName,
1066: String keyName) {
1067: return entityClsName + NAME_SEPARATOR + keyName;
1068: }
1069:
1070: static String makePriDbName(String storePrefix, String entityClsName) {
1071: return storePrefix + entityClsName;
1072: }
1073:
1074: static String makeSecDbName(String storePrefix,
1075: String entityClsName, String keyName) {
1076: return storePrefix + makeSecName(entityClsName, keyName);
1077: }
1078:
1079: private void checkOpen() {
1080: if (catalog == null) {
1081: throw new IllegalStateException("Store has been closed");
1082: }
1083: }
1084:
1085: private EntityMetadata checkEntityClass(String clsName) {
1086: EntityMetadata meta = model.getEntityMetadata(clsName);
1087: if (meta == null) {
1088: throw new IllegalArgumentException(
1089: "Class could not be loaded or is not an entity class: "
1090: + clsName);
1091: }
1092: return meta;
1093: }
1094:
1095: private SecondaryKeyMetadata checkSecKey(EntityMetadata entityMeta,
1096: String keyName) {
1097: SecondaryKeyMetadata secKeyMeta = entityMeta.getSecondaryKeys()
1098: .get(keyName);
1099: if (secKeyMeta == null) {
1100: throw new IllegalArgumentException("Not a secondary key: "
1101: + makeSecName(entityMeta.getClassName(), keyName));
1102: }
1103: return secKeyMeta;
1104: }
1105:
1106: private String getSecKeyClass(SecondaryKeyMetadata secKeyMeta) {
1107: String clsName = secKeyMeta.getElementClassName();
1108: if (clsName == null) {
1109: clsName = secKeyMeta.getClassName();
1110: }
1111: return SimpleCatalog.keyClassName(clsName);
1112: }
1113:
1114: private PersistKeyBinding getKeyBinding(String keyClassName) {
1115: PersistKeyBinding binding = keyBindingMap.get(keyClassName);
1116: if (binding == null) {
1117: binding = new PersistKeyBinding(catalog, keyClassName,
1118: rawAccess);
1119: keyBindingMap.put(keyClassName, binding);
1120: }
1121: return binding;
1122: }
1123:
1124: private void setBtreeComparator(DatabaseConfig config,
1125: String clsName) {
1126: if (!rawAccess) {
1127: ClassMetadata meta = model.getClassMetadata(clsName);
1128: if (meta != null) {
1129: List<FieldMetadata> compositeKeyFields = meta
1130: .getCompositeKeyFields();
1131: if (compositeKeyFields != null) {
1132: Class keyClass = SimpleCatalog
1133: .keyClassForName(clsName);
1134: if (Comparable.class.isAssignableFrom(keyClass)) {
1135: Comparator<Object> cmp = new PersistComparator(
1136: clsName, compositeKeyFields,
1137: getKeyBinding(clsName));
1138: config.setBtreeComparator(cmp);
1139: }
1140: }
1141: }
1142: }
1143: }
1144:
1145: private DatabaseException closeDb(Database db,
1146: DatabaseException firstException) {
1147: if (db != null) {
1148: try {
1149: db.close();
1150: } catch (DatabaseException e) {
1151: if (firstException == null) {
1152: firstException = e;
1153: }
1154: }
1155: }
1156: return firstException;
1157: }
1158:
1159: public EvolveStats evolve(EvolveConfig config)
1160: throws DatabaseException {
1161:
1162: checkOpen();
1163: List<Format> toEvolve = new ArrayList<Format>();
1164: Set<String> configToEvolve = config.getClassesToEvolve();
1165: if (configToEvolve.isEmpty()) {
1166: catalog.getEntityFormats(toEvolve);
1167: } else {
1168: for (String name : configToEvolve) {
1169: Format format = catalog.getFormat(name);
1170: if (format == null) {
1171: throw new IllegalArgumentException(
1172: "Class to evolve is not persistent: "
1173: + name);
1174: }
1175: if (!format.isEntity()) {
1176: throw new IllegalArgumentException(
1177: "Class to evolve is not an entity class: "
1178: + name);
1179: }
1180: toEvolve.add(format);
1181: }
1182: }
1183:
1184: EvolveEvent event = EvolveInternal.newEvent();
1185: for (Format format : toEvolve) {
1186: if (format.getEvolveNeeded()) {
1187: evolveIndex(format, event, config.getEvolveListener());
1188: format.setEvolveNeeded(false);
1189: catalog.flush();
1190: }
1191: }
1192:
1193: return event.getStats();
1194: }
1195:
1196: private void evolveIndex(Format format, EvolveEvent event,
1197: EvolveListener listener) throws DatabaseException {
1198:
1199: Class entityClass = format.getType();
1200: String entityClassName = format.getClassName();
1201: EntityMetadata meta = model.getEntityMetadata(entityClassName);
1202: String keyClassName = meta.getPrimaryKey().getClassName();
1203: keyClassName = SimpleCatalog.keyClassName(keyClassName);
1204: DatabaseConfig dbConfig = getPrimaryConfig(meta);
1205:
1206: PrimaryIndex<Object, Object> index = getPrimaryIndex(
1207: Object.class, keyClassName, entityClass,
1208: entityClassName);
1209: Database db = index.getDatabase();
1210:
1211: EntityBinding binding = index.getEntityBinding();
1212: DatabaseEntry key = new DatabaseEntry();
1213: DatabaseEntry data = new DatabaseEntry();
1214:
1215: Cursor readCursor = db.openCursor(null,
1216: CursorConfig.READ_UNCOMMITTED);
1217: try {
1218: while (readCursor.getNext(key, data, null) == OperationStatus.SUCCESS) {
1219: if (evolveNeeded(key, data, binding)) {
1220: Transaction txn = null;
1221: if (dbConfig.getTransactional()) {
1222: boolean success = false;
1223: txn = env.beginTransaction(null, null);
1224: }
1225: boolean written = false;
1226: Cursor writeCursor = null;
1227: try {
1228: writeCursor = db.openCursor(txn, null);
1229: if (writeCursor.getSearchKey(key, data,
1230: LockMode.RMW) == OperationStatus.SUCCESS) {
1231: if (evolveNeeded(key, data, binding)) {
1232: writeCursor.putCurrent(data);
1233: written = true;
1234: }
1235: if (listener != null) {
1236: EvolveInternal.updateEvent(event,
1237: entityClassName, 1, written ? 1
1238: : 0);
1239: if (!listener.evolveProgress(event)) {
1240: break;
1241: }
1242: }
1243: }
1244: } finally {
1245: if (writeCursor != null) {
1246: writeCursor.close();
1247: }
1248: if (txn != null) {
1249: if (written) {
1250: txn.commit();
1251: } else {
1252: txn.abort();
1253: }
1254: }
1255: }
1256: }
1257: }
1258: } finally {
1259: readCursor.close();
1260: }
1261: }
1262:
1263: /**
1264: * Checks whether the given data is in the current format by translating it
1265: * to/from an object. If true is returned, data is updated.
1266: */
1267: private boolean evolveNeeded(DatabaseEntry key, DatabaseEntry data,
1268: EntityBinding binding) {
1269: Object entity = binding.entryToObject(key, data);
1270: DatabaseEntry newData = new DatabaseEntry();
1271: binding.objectToData(entity, newData);
1272: if (data.equals(newData)) {
1273: return false;
1274: } else {
1275: byte[] bytes = newData.getData();
1276: int off = newData.getOffset();
1277: int size = newData.getSize();
1278: data.setData(bytes, off, size);
1279: return true;
1280: }
1281: }
1282:
1283: /**
1284: * For unit testing.
1285: */
1286: public static void setSyncHook(SyncHook hook) {
1287: syncHook = hook;
1288: }
1289:
1290: /**
1291: * For unit testing.
1292: */
1293: public interface SyncHook {
1294: void onSync(Database db, boolean flushLog);
1295: }
1296: }
|