~ubuntu-branches/ubuntu/saucy/db/saucy-proposed

« back to all changes in this revision

Viewing changes to java/src/com/sleepycat/persist/impl/Store.java

  • Committer: Bazaar Package Importer
  • Author(s): Colin Watson
  • Date: 2010-11-05 15:02:09 UTC
  • mfrom: (13.1.12 sid)
  • Revision ID: james.westby@ubuntu.com-20101105150209-ppvyn0619pu014xo
Tags: 5.1.19-1ubuntu1
* Resynchronise with Debian.  Remaining changes:
  - Pass --build/--host to configure to support cross-building, and don't
    override CC.
  - Disable the Java build when cross-building, for now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*-
2
 
 * See the file LICENSE for redistribution information.
3
 
 *
4
 
 * Copyright (c) 2002, 2010 Oracle and/or its affiliates.  All rights reserved.
5
 
 *
6
 
 * $Id$
7
 
 */
8
 
 
9
 
package com.sleepycat.persist.impl;
10
 
 
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;
16
 
import java.util.Map;
17
 
import java.util.Set;
18
 
import java.util.WeakHashMap;
19
 
 
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;
60
 
 
61
 
/**
62
 
 * Base implementation for EntityStore and  RawStore.  The methods here
63
 
 * correspond directly to those in EntityStore; see EntityStore documentation
64
 
 * for details.
65
 
 *
66
 
 * @author Mark Hayes
67
 
 */
68
 
public class Store {
69
 
 
70
 
    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";
75
 
 
76
 
    private static Map<Environment,Map<String,PersistCatalog>> catalogPool =
77
 
        new WeakHashMap<Environment,Map<String,PersistCatalog>>();
78
 
 
79
 
    /* For unit testing. */
80
 
    private static SyncHook syncHook;
81
 
 
82
 
    private Environment env;
83
 
    private boolean rawAccess;
84
 
    private PersistCatalog catalog;
85
 
    private EntityModel model;
86
 
    private Mutations mutations;
87
 
    private StoreConfig storeConfig;
88
 
    private String 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;
100
 
 
101
 
    public Store(Environment env,
102
 
                 String storeName,
103
 
                 StoreConfig config,
104
 
                 boolean rawAccess)
105
 
        throws StoreExistsException,
106
 
               StoreNotFoundException,
107
 
               IncompatibleClassException,
108
 
               DatabaseException {
109
 
 
110
 
        this.env = env;
111
 
        this.storeName = storeName;
112
 
        this.rawAccess = rawAccess;
113
 
 
114
 
        if (env == null || storeName == null) {
115
 
            throw new NullPointerException
116
 
                ("env and storeName parameters must not be null");
117
 
        }
118
 
        if (config != null) {
119
 
            model = config.getModel();
120
 
            mutations = config.getMutations();
121
 
        }
122
 
        if (config == null) {
123
 
            storeConfig = StoreConfig.DEFAULT;
124
 
        } else {
125
 
            storeConfig = config.cloneConfig();
126
 
        }
127
 
 
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>();
137
 
 
138
 
        if (rawAccess) {
139
 
            /* Open a read-only catalog that uses the stored model. */
140
 
            if (model != null) {
141
 
                throw new IllegalArgumentException
142
 
                    ("A model may not be specified when opening a RawStore");
143
 
            }
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);
151
 
        } else {
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);
158
 
                }
159
 
                catalog = catalogMap.get(storeName);
160
 
                if (catalog != null) {
161
 
                    catalog.openExisting();
162
 
                } else {
163
 
                    Transaction txn = null;
164
 
                    if (storeConfig.getTransactional() &&
165
 
                        DbCompat.getThreadTransaction(env) == null) {
166
 
                        txn = env.beginTransaction(null, null);
167
 
                    }
168
 
                    boolean success = false;
169
 
                    try {
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);
182
 
                        success = true;
183
 
                    } finally {
184
 
                        if (txn != null) {
185
 
                            if (success) {
186
 
                                txn.commit();
187
 
                            } else {
188
 
                                txn.abort();
189
 
                            }
190
 
                        }
191
 
                    }
192
 
                }
193
 
            }
194
 
        }
195
 
 
196
 
        /* Get the merged mutations from the catalog. */
197
 
        mutations = catalog.getMutations();
198
 
 
199
 
        /*
200
 
         * If there is no model parameter, use the default or stored model
201
 
         * obtained from the catalog.
202
 
         */
203
 
        model = catalog.getResolvedModel();
204
 
 
205
 
        /*
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
209
 
         * initialized.
210
 
         */
211
 
        ModelInternal.setCatalog(model, catalog);
212
 
        for (Converter converter : mutations.getConverters()) {
213
 
            converter.getConversion().initialize(model);
214
 
        }
215
 
 
216
 
        /*
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]
225
 
         */
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);
241
 
                    }
242
 
                    inverseClassNames.add(entityMeta.getClassName());
243
 
                }
244
 
            }
245
 
        }
246
 
    }
247
 
 
248
 
    public Environment getEnvironment() {
249
 
        return env;
250
 
    }
251
 
 
252
 
    public StoreConfig getConfig() {
253
 
        return storeConfig.cloneConfig();
254
 
    }
255
 
 
256
 
    public String getStoreName() {
257
 
        return storeName;
258
 
    }
259
 
 
260
 
 
261
 
    public EntityModel getModel() {
262
 
        return model;
263
 
    }
264
 
 
265
 
    public Mutations getMutations() {
266
 
        return mutations;
267
 
    }
268
 
 
269
 
    /**
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
275
 
     * may not be null.
276
 
     */
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 {
283
 
 
284
 
        assert (rawAccess && entityClass == RawObject.class) ||
285
 
              (!rawAccess && entityClass != RawObject.class);
286
 
        assert (rawAccess && primaryKeyClassName == null) ||
287
 
              (!rawAccess && primaryKeyClassName != null);
288
 
 
289
 
        checkOpen();
290
 
 
291
 
        PrimaryIndex<PK,E> priIndex = priIndexMap.get(entityClassName);
292
 
        if (priIndex == null) {
293
 
 
294
 
            /* Check metadata. */
295
 
            EntityMetadata entityMeta = checkEntityClass(entityClassName);
296
 
            PrimaryKeyMetadata priKeyMeta = entityMeta.getPrimaryKey();
297
 
            if (primaryKeyClassName == null) {
298
 
                primaryKeyClassName = priKeyMeta.getClassName();
299
 
            } else {
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);
306
 
                }
307
 
            }
308
 
 
309
 
            /* Create bindings. */
310
 
            PersistEntityBinding entityBinding =
311
 
                new PersistEntityBinding(catalog, entityClassName, rawAccess);
312
 
            PersistKeyBinding keyBinding = getKeyBinding(primaryKeyClassName);
313
 
 
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));
319
 
            }
320
 
 
321
 
            /*
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.
326
 
             */
327
 
            Transaction txn = null;
328
 
            DatabaseConfig dbConfig = getPrimaryConfig(entityMeta);
329
 
            if (dbConfig.getTransactional() &&
330
 
                DbCompat.getThreadTransaction(env) == null) {
331
 
                txn = env.beginTransaction(null, null);
332
 
            }
333
 
            PrimaryOpenState priOpenState =
334
 
                new PrimaryOpenState(entityClassName);
335
 
            boolean success = false;
336
 
            try {
337
 
        
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);
343
 
                assert db != null;
344
 
                priOpenState.addDatabase(db);
345
 
 
346
 
                /* Create index object. */
347
 
                priIndex = new PrimaryIndex
348
 
                    (db, primaryKeyClass, keyBinding, entityClass,
349
 
                     entityBinding);
350
 
 
351
 
                /* Update index and database maps. */
352
 
                priIndexMap.put(entityClassName, priIndex);
353
 
                if (DbCompat.getDeferredWrite(dbConfig)) {
354
 
                    deferredWriteDatabases.put(db, null);
355
 
                }
356
 
 
357
 
                /* If not read-only, open all associated secondaries. */
358
 
                if (!dbConfig.getReadOnly()) {
359
 
                    openSecondaryIndexes(txn, entityMeta, priOpenState);
360
 
 
361
 
                    /*
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]
365
 
                     */
366
 
                    Set<String> inverseClassNames =
367
 
                        inverseRelatedEntityMap.get(entityClassName);
368
 
                    if (inverseClassNames != null) {
369
 
                        for (String relatedClsName : inverseClassNames) {
370
 
                            getRelatedIndex(relatedClsName);
371
 
                        }
372
 
                    }
373
 
                }
374
 
                success = true;
375
 
            } finally {
376
 
                if (success) {
377
 
                    if (txn != null) {
378
 
                        txn.commit();
379
 
                    }
380
 
                } else {
381
 
                    if (txn != null) {
382
 
                        txn.abort();
383
 
                    }
384
 
                    priOpenState.undoState();
385
 
                }
386
 
            }
387
 
        }
388
 
        return priIndex;
389
 
    }
390
 
 
391
 
    /**
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.
396
 
     */
397
 
    private class PrimaryOpenState {
398
 
 
399
 
        private String entityClassName;
400
 
        private IdentityHashMap<Database,Object> databases;
401
 
        private Set<String> secNames;
402
 
 
403
 
        PrimaryOpenState(String entityClassName) {
404
 
            this.entityClassName = entityClassName;
405
 
            databases = new IdentityHashMap<Database,Object>();
406
 
            secNames = new HashSet<String>();
407
 
        }
408
 
 
409
 
        /**
410
 
         * Save a database that was opening during this operation.
411
 
         */
412
 
        void addDatabase(Database db) {
413
 
            databases.put(db, null);
414
 
        }
415
 
 
416
 
        /**
417
 
         * Save the name of a secondary index that was opening during this
418
 
         * operation.
419
 
         */
420
 
        void addSecondaryName(String secName) {
421
 
            secNames.add(secName);
422
 
        }
423
 
 
424
 
        /**
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.
428
 
         *
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.
432
 
         */
433
 
        void undoState() {
434
 
            for (Database db : databases.keySet()) {
435
 
                try {
436
 
                    db.close();
437
 
                } catch (Exception ignored) {
438
 
                }
439
 
            }
440
 
            priIndexMap.remove(entityClassName);
441
 
            for (String secName : secNames) {
442
 
                secIndexMap.remove(secName);
443
 
            }
444
 
            for (Database db : databases.keySet()) {
445
 
                deferredWriteDatabases.remove(db);
446
 
            }
447
 
        }
448
 
    }
449
 
 
450
 
    /**
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.
456
 
     */
457
 
    private PrimaryIndex getRelatedIndex(String relatedClsName)
458
 
        throws DatabaseException {
459
 
 
460
 
        PrimaryIndex relatedIndex = priIndexMap.get(relatedClsName);
461
 
        if (relatedIndex == null) {
462
 
            EntityMetadata relatedEntityMeta =
463
 
                checkEntityClass(relatedClsName);
464
 
            Class relatedKeyCls;
465
 
            String relatedKeyClsName;
466
 
            Class relatedCls;
467
 
            if (rawAccess) {
468
 
                relatedCls = RawObject.class;
469
 
                relatedKeyCls = Object.class;
470
 
                relatedKeyClsName = null;
471
 
            } else {
472
 
                try {
473
 
                    relatedCls = EntityModel.classForName(relatedClsName);
474
 
                } catch (ClassNotFoundException e) {
475
 
                    throw new IllegalArgumentException
476
 
                        ("Related entity class not found: " +
477
 
                         relatedClsName);
478
 
                }
479
 
                relatedKeyClsName = SimpleCatalog.keyClassName
480
 
                    (relatedEntityMeta.getPrimaryKey().getClassName());
481
 
                relatedKeyCls =
482
 
                    SimpleCatalog.keyClassForName(relatedKeyClsName);
483
 
            }
484
 
 
485
 
            /*
486
 
             * Cycles are prevented here by adding primary indexes to the
487
 
             * priIndexMap as soon as they are created, before opening related
488
 
             * indexes.
489
 
             */
490
 
            relatedIndex = getPrimaryIndex
491
 
                (relatedKeyCls, relatedKeyClsName,
492
 
                 relatedCls, relatedClsName);
493
 
        }
494
 
        return relatedIndex;
495
 
    }
496
 
 
497
 
    /**
498
 
     * A getSecondaryIndex with extra parameters for opening a raw store.
499
 
     * keyClassName is used for consistency checking and should be null for a
500
 
     * raw store only.
501
 
     */
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,
506
 
                          Class<SK> keyClass,
507
 
                          String keyClassName,
508
 
                          String keyName)
509
 
        throws DatabaseException {
510
 
 
511
 
        assert (rawAccess && keyClassName == null) ||
512
 
              (!rawAccess && keyClassName != null);
513
 
 
514
 
        checkOpen();
515
 
 
516
 
        EntityMetadata entityMeta = null;
517
 
        SecondaryKeyMetadata secKeyMeta = null;
518
 
 
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));
531
 
            }
532
 
 
533
 
            /*
534
 
             * Get/create the subclass format to ensure it is stored in the
535
 
             * catalog, even if no instances of the subclass are stored.
536
 
             * [#16399]
537
 
             */
538
 
            catalog.getFormat(entityClass,
539
 
                              false /*checkEntitySubclassIndexes*/);
540
 
        }
541
 
 
542
 
        /*
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.
547
 
         */
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;
554
 
            }
555
 
            if (secKeyMeta == null) {
556
 
                secKeyMeta = checkSecKey(entityMeta, keyName);
557
 
            }
558
 
 
559
 
            /* Check metadata. */
560
 
            if (keyClassName == null) {
561
 
                keyClassName = getSecKeyClass(secKeyMeta);
562
 
            } else {
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);
568
 
                }
569
 
            }
570
 
 
571
 
            secIndex = openSecondaryIndex
572
 
                (null, primaryIndex, entityClass, entityMeta,
573
 
                 keyClass, keyClassName, secKeyMeta, secName,
574
 
                 false /*doNotCreate*/, null /*priOpenState*/);
575
 
        }
576
 
        return secIndex;
577
 
    }
578
 
 
579
 
    /**
580
 
     * Opens secondary indexes for a given primary index metadata.
581
 
     */
582
 
    private void openSecondaryIndexes(Transaction txn,
583
 
                                      EntityMetadata entityMeta,
584
 
                                      PrimaryOpenState priOpenState)
585
 
        throws DatabaseException {
586
 
 
587
 
        String entityClassName = entityMeta.getClassName();
588
 
        PrimaryIndex<Object,Object> priIndex =
589
 
            priIndexMap.get(entityClassName);
590
 
        assert priIndex != null;
591
 
        Class<Object> entityClass = priIndex.getEntityClass();
592
 
 
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. */
602
 
                Class keyClass =
603
 
                    SimpleCatalog.keyClassForName(keyClassName);
604
 
                openSecondaryIndex
605
 
                    (txn, priIndex, entityClass, entityMeta,
606
 
                     keyClass, keyClassName, secKeyMeta,
607
 
                     makeSecName
608
 
                        (entityClassName, secKeyMeta.getKeyName()),
609
 
                     storeConfig.getSecondaryBulkLoad() /*doNotCreate*/,
610
 
                     priOpenState);
611
 
            }
612
 
        }
613
 
    }
614
 
 
615
 
    /**
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.
618
 
     */
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,
624
 
                           Class<SK> keyClass,
625
 
                           String keyClassName,
626
 
                           SecondaryKeyMetadata secKeyMeta,
627
 
                           String secName,
628
 
                           boolean doNotCreate,
629
 
                           PrimaryOpenState priOpenState)
630
 
        throws DatabaseException {
631
 
 
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();
638
 
 
639
 
        String relatedClsName = secKeyMeta.getRelatedEntity();
640
 
        if (relatedClsName != null) {
641
 
            PrimaryIndex relatedIndex = getRelatedIndex(relatedClsName);
642
 
            config.setForeignKeyDatabase(relatedIndex.getDatabase());
643
 
        }
644
 
 
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");
653
 
        }
654
 
 
655
 
        PersistKeyBinding keyBinding = getKeyBinding(keyClassName);
656
 
        
657
 
        SecondaryDatabase db = openSecondaryDatabase
658
 
            (txn, fileAndDbNames, priDb, config, doNotCreate);
659
 
        if (db == null) {
660
 
            assert doNotCreate;
661
 
            return null;
662
 
        }
663
 
        SecondaryIndex<SK,PK,E2> secIndex = new SecondaryIndex
664
 
            (db, null, primaryIndex, keyClass, keyBinding);
665
 
 
666
 
        /* Update index and database maps. */
667
 
        secIndexMap.put(secName, secIndex);
668
 
        if (DbCompat.getDeferredWrite(config)) {
669
 
            deferredWriteDatabases.put(db, null);
670
 
        }
671
 
        if (priOpenState != null) {
672
 
            priOpenState.addDatabase(db);
673
 
            priOpenState.addSecondaryName(secName);
674
 
        }
675
 
        return secIndex;
676
 
    }
677
 
 
678
 
    /**
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]
683
 
     *
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 --
690
 
     * a bulk load.
691
 
     */
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 {
699
 
 
700
 
        assert config.getAllowPopulate();
701
 
        assert !config.getExclusiveCreate();
702
 
        final boolean saveAllowCreate = config.getAllowCreate();
703
 
        try {
704
 
            if (doNotCreate) {
705
 
                config.setAllowCreate(false);
706
 
            }
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,
713
 
                     config);
714
 
                if (db != null) {
715
 
                    return db;
716
 
                }
717
 
            }
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,
724
 
                 config);
725
 
            return db;
726
 
        } finally {
727
 
            config.setAllowPopulate(true);
728
 
            config.setExclusiveCreate(false);
729
 
            config.setAllowCreate(saveAllowCreate);
730
 
        }
731
 
    }
732
 
 
733
 
    /**
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]
737
 
     *
738
 
     * @throws IllegalArgumentException if a secondary is not open.
739
 
     */
740
 
    synchronized void
741
 
        checkEntitySubclassSecondaries(final EntityMetadata entityMeta,
742
 
                                       final String subclassName)
743
 
        throws DatabaseException {
744
 
        
745
 
        if (storeConfig.getSecondaryBulkLoad()) {
746
 
            return;
747
 
        }
748
 
 
749
 
        final String entityClassName = entityMeta.getClassName();
750
 
 
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);
761
 
            }
762
 
        }
763
 
    }
764
 
 
765
 
 
766
 
    public void truncateClass(Class entityClass)
767
 
        throws DatabaseException {
768
 
 
769
 
        truncateClass(null, entityClass);
770
 
    }
771
 
 
772
 
    public synchronized void truncateClass(Transaction txn, Class entityClass)
773
 
        throws DatabaseException {
774
 
 
775
 
        checkOpen();
776
 
 
777
 
        /* Close primary and secondary databases. */
778
 
        closeClass(entityClass);
779
 
 
780
 
        String clsName = entityClass.getName();
781
 
        EntityMetadata entityMeta = checkEntityClass(clsName);
782
 
 
783
 
        /*
784
 
         * Truncate the primary first and let any exceptions propogate
785
 
         * upwards.  Then remove each secondary, only throwing the first
786
 
         * exception.
787
 
         */
788
 
        boolean primaryExists = truncateIfExists(txn, storePrefix + clsName);
789
 
        if (primaryExists) {
790
 
            DatabaseException firstException = null;
791
 
            for (SecondaryKeyMetadata keyMeta :
792
 
                 entityMeta.getSecondaryKeys().values()) {
793
 
                /* Ignore secondaries that do not exist. */
794
 
                removeIfExists
795
 
                    (txn,
796
 
                     storePrefix +
797
 
                     makeSecName(clsName, keyMeta.getKeyName()));
798
 
            }
799
 
            if (firstException != null) {
800
 
                throw firstException;
801
 
            }
802
 
        }
803
 
    }
804
 
 
805
 
    private boolean truncateIfExists(Transaction txn, String dbName)
806
 
        throws DatabaseException {
807
 
 
808
 
        String[] fileAndDbNames = parseDbName(dbName);
809
 
        return DbCompat.truncateDatabase
810
 
            (env, txn, fileAndDbNames[0], fileAndDbNames[1]);
811
 
    }
812
 
 
813
 
    private boolean removeIfExists(Transaction txn, String dbName)
814
 
        throws DatabaseException {
815
 
 
816
 
        String[] fileAndDbNames = parseDbName(dbName);
817
 
        return DbCompat.removeDatabase
818
 
            (env, txn, fileAndDbNames[0], fileAndDbNames[1]);
819
 
    }
820
 
 
821
 
    public synchronized void closeClass(Class entityClass)
822
 
        throws DatabaseException {
823
 
 
824
 
        checkOpen();
825
 
        String clsName = entityClass.getName();
826
 
        EntityMetadata entityMeta = checkEntityClass(clsName);
827
 
 
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()) {
834
 
 
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);
840
 
                    firstException =
841
 
                        closeDb(secIndex.getKeysDatabase(), firstException);
842
 
                    secIndexMap.remove(secName);
843
 
                    deferredWriteDatabases.remove(db);
844
 
                }
845
 
            }
846
 
            /* Close the primary last. */
847
 
            Database db = priIndex.getDatabase();
848
 
            firstException = closeDb(db, firstException);
849
 
            priIndexMap.remove(clsName);
850
 
            deferredWriteDatabases.remove(db);
851
 
 
852
 
            /* Throw the first exception encountered. */
853
 
            if (firstException != null) {
854
 
                throw firstException;
855
 
            }
856
 
        }
857
 
    }
858
 
 
859
 
    public synchronized void close()
860
 
        throws DatabaseException {
861
 
 
862
 
        if (catalog == null) {
863
 
            return;
864
 
        }
865
 
 
866
 
        DatabaseException firstException = null;
867
 
        try {
868
 
            if (rawAccess) {
869
 
                boolean allClosed = catalog.close();
870
 
                assert allClosed;
871
 
            } else {
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);
879
 
                    }
880
 
                }
881
 
            }
882
 
            catalog = null;
883
 
        } catch (DatabaseException e) {
884
 
            if (firstException == null) {
885
 
                firstException = e;
886
 
            }
887
 
        }
888
 
                for (Sequence seq : sequenceMap.values()) {
889
 
                        try {
890
 
                                seq.close();
891
 
                        } catch (DatabaseException e) {
892
 
                                if (firstException == null) {
893
 
                                        firstException = e;
894
 
                                }
895
 
                        }
896
 
                }               
897
 
        firstException = closeDb(sequenceDb, firstException);
898
 
        for (SecondaryIndex index : secIndexMap.values()) {
899
 
            firstException = closeDb(index.getDatabase(), firstException);
900
 
            firstException = closeDb(index.getKeysDatabase(), firstException);
901
 
        }
902
 
        for (PrimaryIndex index : priIndexMap.values()) {
903
 
            firstException = closeDb(index.getDatabase(), firstException);
904
 
        }
905
 
        if (firstException != null) {
906
 
            throw firstException;
907
 
        }
908
 
    }
909
 
 
910
 
    public synchronized Sequence getSequence(String name)
911
 
        throws DatabaseException {
912
 
 
913
 
        checkOpen();
914
 
 
915
 
        if (storeConfig.getReadOnly()) {
916
 
            throw new IllegalStateException("Store is read-only");
917
 
        }
918
 
 
919
 
        Sequence seq = sequenceMap.get(name);
920
 
        if (seq == null) {
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;
932
 
            }
933
 
            DatabaseEntry entry = new DatabaseEntry();
934
 
            StringBinding.stringToEntry(name, entry);
935
 
                seq = sequenceDb.openSequence(null, entry,
936
 
                                              getSequenceConfig(name));
937
 
            sequenceMap.put(name, seq);
938
 
        }
939
 
        return seq;
940
 
    }
941
 
 
942
 
    public synchronized SequenceConfig getSequenceConfig(String name) {
943
 
        checkOpen();
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);
953
 
        }
954
 
        return config;
955
 
    }
956
 
 
957
 
    public synchronized void setSequenceConfig(String name,
958
 
                                               SequenceConfig config) {
959
 
        checkOpen();
960
 
        if (config.getExclusiveCreate() ||
961
 
            config.getAllowCreate() == storeConfig.getReadOnly()) {
962
 
            throw new IllegalArgumentException
963
 
                ("One of these properties was illegally changed: " +
964
 
                 "AllowCreate, ExclusiveCreate");
965
 
        }
966
 
        sequenceConfigMap.put(name, config);
967
 
    }
968
 
 
969
 
    public synchronized DatabaseConfig getPrimaryConfig(Class entityClass) {
970
 
        checkOpen();
971
 
        String clsName = entityClass.getName();
972
 
        EntityMetadata meta = checkEntityClass(clsName);
973
 
        return getPrimaryConfig(meta).cloneConfig();
974
 
    }
975
 
 
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);
987
 
        }
988
 
        return config;
989
 
    }
990
 
 
991
 
    public synchronized void setPrimaryConfig(Class entityClass,
992
 
                                              DatabaseConfig config) {
993
 
        checkOpen();
994
 
        String clsName = entityClass.getName();
995
 
        if (priIndexMap.containsKey(clsName)) {
996
 
            throw new IllegalStateException
997
 
                ("Cannot set config after DB is open");
998
 
        }
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, ");
1009
 
        }
1010
 
        if (!DbCompat.isTypeBtree(config)) {
1011
 
            throw new IllegalArgumentException("Only type BTREE allowed");
1012
 
        }
1013
 
        priConfigMap.put(clsName, config);
1014
 
    }
1015
 
 
1016
 
    public synchronized SecondaryConfig getSecondaryConfig(Class entityClass,
1017
 
                                                           String keyName) {
1018
 
        checkOpen();
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();
1026
 
    }
1027
 
 
1028
 
    private SecondaryConfig getSecondaryConfig(String secName,
1029
 
                                               EntityMetadata entityMeta,
1030
 
                                               String keyClassName,
1031
 
                                               SecondaryKeyMetadata
1032
 
                                               secKeyMeta) {
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);
1053
 
            } else {
1054
 
                config.setKeyCreator(keyCreator);
1055
 
            }
1056
 
            DeleteAction deleteAction = secKeyMeta.getDeleteAction();
1057
 
            if (deleteAction != null) {
1058
 
                ForeignKeyDeleteAction baseDeleteAction;
1059
 
                switch (deleteAction) {
1060
 
                case ABORT:
1061
 
                    baseDeleteAction = ForeignKeyDeleteAction.ABORT;
1062
 
                    break;
1063
 
                case CASCADE:
1064
 
                    baseDeleteAction = ForeignKeyDeleteAction.CASCADE;
1065
 
                    break;
1066
 
                case NULLIFY:
1067
 
                    baseDeleteAction = ForeignKeyDeleteAction.NULLIFY;
1068
 
                    break;
1069
 
                default:
1070
 
                    throw new IllegalStateException(deleteAction.toString());
1071
 
                }
1072
 
                config.setForeignKeyDeleteAction(baseDeleteAction);
1073
 
                if (deleteAction == DeleteAction.NULLIFY) {
1074
 
                    config.setForeignMultiKeyNullifier(keyCreator);
1075
 
                }
1076
 
            }
1077
 
            secConfigMap.put(secName, config);
1078
 
        }
1079
 
        return config;
1080
 
    }
1081
 
 
1082
 
    public synchronized void setSecondaryConfig(Class entityClass,
1083
 
                                                String keyName,
1084
 
                                                SecondaryConfig config) {
1085
 
        checkOpen();
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");
1094
 
        }
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");
1119
 
        }
1120
 
        if (!DbCompat.isTypeBtree(config)) {
1121
 
            throw new IllegalArgumentException("Only type BTREE allowed");
1122
 
        }
1123
 
        secConfigMap.put(secName, config);
1124
 
    }
1125
 
 
1126
 
    private static String makeSecName(String entityClsName, String keyName) {
1127
 
         return entityClsName + NAME_SEPARATOR + keyName;
1128
 
    }
1129
 
 
1130
 
    static String makePriDbName(String storePrefix, String entityClsName) {
1131
 
        return storePrefix + entityClsName;
1132
 
    }
1133
 
 
1134
 
    static String makeSecDbName(String storePrefix,
1135
 
                                String entityClsName,
1136
 
                                String keyName) {
1137
 
        return storePrefix + makeSecName(entityClsName, keyName);
1138
 
    }
1139
 
 
1140
 
    /**
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
1144
 
     * for DB core).
1145
 
     */
1146
 
    public String[] parseDbName(String wholeName) {
1147
 
        return parseDbName(wholeName, storeConfig.getDatabaseNamer());
1148
 
    }
1149
 
 
1150
 
    /**
1151
 
     * Allows passing a namer to a static method for testing.
1152
 
     */
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);
1163
 
            result[1] = null;
1164
 
        } else {
1165
 
            result[0] = null;
1166
 
            result[1] = wholeName;
1167
 
        }
1168
 
        return result;
1169
 
    }
1170
 
 
1171
 
    /**
1172
 
     * Creates a message identifying the database from the pair of strings
1173
 
     * returned by parseDbName.
1174
 
     */
1175
 
    String getDbNameMessage(String[] names) {
1176
 
        if (DbCompat.SEPARATE_DATABASE_FILES) {
1177
 
            return "file: " + names[0];
1178
 
        } else {
1179
 
            return "database: " + names[1];
1180
 
        }
1181
 
    }
1182
 
 
1183
 
    private void checkOpen() {
1184
 
        if (catalog == null) {
1185
 
            throw new IllegalStateException("Store has been closed");
1186
 
        }
1187
 
    }
1188
 
 
1189
 
    private EntityMetadata checkEntityClass(String clsName) {
1190
 
        EntityMetadata meta = model.getEntityMetadata(clsName);
1191
 
        if (meta == null) {
1192
 
            throw new IllegalArgumentException
1193
 
                ("Class could not be loaded or is not an entity class: " +
1194
 
                 clsName);
1195
 
        }
1196
 
        return meta;
1197
 
    }
1198
 
 
1199
 
    private SecondaryKeyMetadata checkSecKey(EntityMetadata entityMeta,
1200
 
                                             String keyName) {
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));
1207
 
        }
1208
 
        return secKeyMeta;
1209
 
    }
1210
 
 
1211
 
    private String getSecKeyClass(SecondaryKeyMetadata secKeyMeta) {
1212
 
        String clsName = secKeyMeta.getElementClassName();
1213
 
        if (clsName == null) {
1214
 
            clsName = secKeyMeta.getClassName();
1215
 
        }
1216
 
        return SimpleCatalog.keyClassName(clsName);
1217
 
    }
1218
 
 
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);
1224
 
        }
1225
 
        return binding;
1226
 
    }
1227
 
 
1228
 
    private void setBtreeComparator(DatabaseConfig config, String clsName) {
1229
 
        if (!rawAccess) {
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));
1236
 
                }
1237
 
            }
1238
 
        }
1239
 
    }
1240
 
 
1241
 
    private DatabaseException closeDb(Database db,
1242
 
                                      DatabaseException firstException) {
1243
 
        if (db != null) {
1244
 
            try {
1245
 
                db.close();
1246
 
            } catch (DatabaseException e) {
1247
 
                if (firstException == null) {
1248
 
                    firstException = e;
1249
 
                }
1250
 
            }
1251
 
        }
1252
 
        return firstException;
1253
 
    }
1254
 
 
1255
 
    public EvolveStats evolve(EvolveConfig config)
1256
 
        throws DatabaseException {
1257
 
 
1258
 
        checkOpen();
1259
 
        List<Format> toEvolve = new ArrayList<Format>();
1260
 
        Set<String> configToEvolve = config.getClassesToEvolve();
1261
 
        if (configToEvolve.isEmpty()) {
1262
 
            catalog.getEntityFormats(toEvolve);
1263
 
        } else {
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);
1269
 
                }
1270
 
                if (!format.isEntity()) {
1271
 
                    throw new IllegalArgumentException
1272
 
                        ("Class to evolve is not an entity class: " + name);
1273
 
                }
1274
 
                toEvolve.add(format);
1275
 
            }
1276
 
        }
1277
 
 
1278
 
        EvolveEvent event = EvolveInternal.newEvent();
1279
 
        for (Format format : toEvolve) {
1280
 
            if (format.getEvolveNeeded()) {
1281
 
                evolveIndex(format, event, config.getEvolveListener());
1282
 
                format.setEvolveNeeded(false);
1283
 
                catalog.flush();
1284
 
            }
1285
 
        }
1286
 
 
1287
 
        return event.getStats();
1288
 
    }
1289
 
 
1290
 
    private void evolveIndex(Format format,
1291
 
                             EvolveEvent event,
1292
 
                             EvolveListener listener)
1293
 
        throws DatabaseException {
1294
 
 
1295
 
        /* We may make this configurable later. */
1296
 
        final int WRITES_PER_TXN = 1;
1297
 
 
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);
1304
 
 
1305
 
        PrimaryIndex<Object,Object> index = getPrimaryIndex
1306
 
            (Object.class, keyClassName, entityClass, entityClassName);
1307
 
        Database db = index.getDatabase();
1308
 
 
1309
 
        EntityBinding binding = index.getEntityBinding();
1310
 
        DatabaseEntry key = new DatabaseEntry();
1311
 
        DatabaseEntry data = new DatabaseEntry();
1312
 
 
1313
 
        CursorConfig cursorConfig = null;
1314
 
        Transaction txn = null;
1315
 
        if (dbConfig.getTransactional()) {
1316
 
            txn = env.beginTransaction(null, null);
1317
 
            cursorConfig = CursorConfig.READ_COMMITTED;
1318
 
        }
1319
 
 
1320
 
        Cursor cursor = null;
1321
 
        int nWritten = 0;
1322
 
        try {
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);
1329
 
                    oneWritten = true;
1330
 
                    nWritten += 1;
1331
 
                }
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)) {
1337
 
                        break;
1338
 
                    }
1339
 
                }
1340
 
                if (txn != null && nWritten >= WRITES_PER_TXN) {
1341
 
                    cursor.close();
1342
 
                    cursor = null;
1343
 
                    txn.commit();
1344
 
                    txn = null;
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);
1352
 
                    }
1353
 
                } else {
1354
 
                    status = cursor.getNext(key, data, null);
1355
 
                }
1356
 
            }
1357
 
        } finally {
1358
 
            if (cursor != null) {
1359
 
                cursor.close();
1360
 
            }
1361
 
            if (txn != null) {
1362
 
                if (nWritten > 0) {
1363
 
                    txn.commit();
1364
 
                } else {
1365
 
                    txn.abort();
1366
 
                }
1367
 
            }
1368
 
        }
1369
 
    }
1370
 
 
1371
 
    /**
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.
1374
 
     */
1375
 
    private boolean evolveNeeded(DatabaseEntry key,
1376
 
                                 DatabaseEntry data,
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)) {
1382
 
            return false;
1383
 
        } else {
1384
 
            byte[] bytes = newData.getData();
1385
 
            int off = newData.getOffset();
1386
 
            int size = newData.getSize();
1387
 
            data.setData(bytes, off, size);
1388
 
            return true;
1389
 
        }
1390
 
    }
1391
 
 
1392
 
    /**
1393
 
     * For unit testing.
1394
 
     */
1395
 
    public static void setSyncHook(SyncHook hook) {
1396
 
        syncHook = hook;
1397
 
    }
1398
 
 
1399
 
    /**
1400
 
     * For unit testing.
1401
 
     */
1402
 
    public interface SyncHook {
1403
 
        void onSync(Database db, boolean flushLog);
1404
 
    }
1405
 
}