• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt-6.x.4708/router/db-4.8.30/java/src/com/sleepycat/persist/impl/
1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002-2009 Oracle.  All rights reserved.
5 *
6 * $Id$
7 */
8
9package com.sleepycat.persist.impl;
10
11import java.util.ArrayList;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.IdentityHashMap;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18import java.util.WeakHashMap;
19
20import com.sleepycat.bind.EntityBinding;
21import com.sleepycat.bind.tuple.StringBinding;
22import com.sleepycat.compat.DbCompat;
23import com.sleepycat.db.Cursor;
24import com.sleepycat.db.CursorConfig;
25import com.sleepycat.db.Database;
26import com.sleepycat.db.DatabaseConfig;
27import com.sleepycat.db.DatabaseEntry;
28import com.sleepycat.db.DatabaseException;
29import com.sleepycat.db.Environment;
30import com.sleepycat.db.ForeignKeyDeleteAction;
31import com.sleepycat.db.OperationStatus;
32import com.sleepycat.db.SecondaryConfig;
33import com.sleepycat.db.SecondaryDatabase;
34import com.sleepycat.db.Sequence;
35import com.sleepycat.db.SequenceConfig;
36import com.sleepycat.db.Transaction;
37import com.sleepycat.persist.DatabaseNamer;
38import com.sleepycat.persist.PrimaryIndex;
39import com.sleepycat.persist.SecondaryIndex;
40import com.sleepycat.persist.StoreConfig;
41import com.sleepycat.persist.StoreExistsException;
42import com.sleepycat.persist.StoreNotFoundException;
43import com.sleepycat.persist.evolve.Converter;
44import com.sleepycat.persist.evolve.EvolveConfig;
45import com.sleepycat.persist.evolve.EvolveEvent;
46import com.sleepycat.persist.evolve.EvolveInternal;
47import com.sleepycat.persist.evolve.EvolveListener;
48import com.sleepycat.persist.evolve.EvolveStats;
49import com.sleepycat.persist.evolve.IncompatibleClassException;
50import com.sleepycat.persist.evolve.Mutations;
51import com.sleepycat.persist.model.DeleteAction;
52import com.sleepycat.persist.model.EntityMetadata;
53import com.sleepycat.persist.model.EntityModel;
54import com.sleepycat.persist.model.ModelInternal;
55import com.sleepycat.persist.model.PrimaryKeyMetadata;
56import com.sleepycat.persist.model.Relationship;
57import com.sleepycat.persist.model.SecondaryKeyMetadata;
58import com.sleepycat.persist.raw.RawObject;
59import 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 */
68public 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}
1406