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