1 8 9 package com.sleepycat.persist.impl; 10 11 import java.util.ArrayList ; 12 import java.util.Comparator ; 13 import java.util.HashMap ; 14 import java.util.HashSet ; 15 import java.util.IdentityHashMap ; 16 import java.util.List ; 17 import java.util.Map ; 18 import java.util.Set ; 19 import java.util.WeakHashMap ; 20 21 import com.sleepycat.bind.EntityBinding; 22 import com.sleepycat.bind.tuple.StringBinding; 23 import com.sleepycat.compat.DbCompat; 24 import com.sleepycat.je.Cursor; 25 import com.sleepycat.je.CursorConfig; 26 import com.sleepycat.je.Database; 27 import com.sleepycat.je.DatabaseConfig; 28 import com.sleepycat.je.DatabaseEntry; 29 import com.sleepycat.je.DatabaseException; 30 import com.sleepycat.je.DatabaseNotFoundException; 31 import com.sleepycat.je.Environment; 32 import com.sleepycat.je.ForeignKeyDeleteAction; 33 import com.sleepycat.je.LockMode; 34 import com.sleepycat.je.OperationStatus; 35 import com.sleepycat.je.SecondaryConfig; 36 import com.sleepycat.je.SecondaryDatabase; 37 import com.sleepycat.je.Sequence; 38 import com.sleepycat.je.SequenceConfig; 39 import com.sleepycat.je.Transaction; 40 import com.sleepycat.persist.PrimaryIndex; 41 import com.sleepycat.persist.SecondaryIndex; 42 import com.sleepycat.persist.StoreConfig; 43 import com.sleepycat.persist.evolve.Converter; 44 import com.sleepycat.persist.evolve.EvolveConfig; 45 import com.sleepycat.persist.evolve.EvolveEvent; 46 import com.sleepycat.persist.evolve.EvolveInternal; 47 import com.sleepycat.persist.evolve.EvolveListener; 48 import com.sleepycat.persist.evolve.EvolveStats; 49 import com.sleepycat.persist.evolve.Mutations; 50 import com.sleepycat.persist.model.ClassMetadata; 51 import com.sleepycat.persist.model.DeleteAction; 52 import com.sleepycat.persist.model.EntityMetadata; 53 import com.sleepycat.persist.model.EntityModel; 54 import com.sleepycat.persist.model.FieldMetadata; 55 import com.sleepycat.persist.model.ModelInternal; 56 import com.sleepycat.persist.model.PrimaryKeyMetadata; 57 import com.sleepycat.persist.model.Relationship; 58 import com.sleepycat.persist.model.SecondaryKeyMetadata; 59 import com.sleepycat.persist.raw.RawObject; 60 61 68 public class Store { 69 70 private static final char 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 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 100 public Store(Environment env, 101 String storeName, 102 StoreConfig config, 103 boolean rawAccess) 104 throws DatabaseException { 105 106 this.env = env; 107 this.storeName = storeName; 108 this.rawAccess = rawAccess; 109 110 if (env == null || storeName == null) { 111 throw new NullPointerException 112 ("env and storeName parameters must not be null"); 113 } 114 if (config != null) { 115 model = config.getModel(); 116 mutations = config.getMutations(); 117 } 118 if (config == null) { 119 storeConfig = StoreConfig.DEFAULT; 120 } else { 121 storeConfig = config.cloneConfig(); 122 } 123 124 storePrefix = NAME_PREFIX + storeName + NAME_SEPARATOR; 125 priIndexMap = new HashMap <String ,PrimaryIndex>(); 126 secIndexMap = new HashMap <String ,SecondaryIndex>(); 127 priConfigMap = new HashMap <String ,DatabaseConfig>(); 128 secConfigMap = new HashMap <String ,SecondaryConfig>(); 129 keyBindingMap = new HashMap <String ,PersistKeyBinding>(); 130 sequenceMap = new HashMap <String ,Sequence>(); 131 sequenceConfigMap = new HashMap <String ,SequenceConfig>(); 132 deferredWriteDatabases = new IdentityHashMap <Database,Object >(); 133 134 if (rawAccess) { 135 136 if (model != null) { 137 throw new IllegalArgumentException 138 ("A model may not be specified when opening a RawStore"); 139 } 140 DatabaseConfig dbConfig = new DatabaseConfig(); 141 dbConfig.setReadOnly(true); 142 dbConfig.setTransactional 143 (storeConfig.getTransactional()); 144 catalog = new PersistCatalog 145 (null, env, storePrefix, storePrefix + CATALOG_DB, dbConfig, 146 model, mutations, rawAccess, this); 147 } else { 148 149 synchronized (catalogPool) { 150 Map <String ,PersistCatalog> catalogMap = catalogPool.get(env); 151 if (catalogMap == null) { 152 catalogMap = new HashMap <String ,PersistCatalog>(); 153 catalogPool.put(env, catalogMap); 154 } 155 catalog = catalogMap.get(storeName); 156 if (catalog != null) { 157 catalog.openExisting(); 158 } else { 159 Transaction txn = null; 160 if (storeConfig.getTransactional() && 161 env.getThreadTransaction() == null) { 162 txn = env.beginTransaction(null, null); 163 } 164 boolean success = false; 165 try { 166 DatabaseConfig dbConfig = new DatabaseConfig(); 167 dbConfig.setAllowCreate(storeConfig.getAllowCreate()); 168 dbConfig.setReadOnly(storeConfig.getReadOnly()); 169 dbConfig.setTransactional 170 (storeConfig.getTransactional()); 171 catalog = new PersistCatalog 172 (txn, env, storePrefix, storePrefix + CATALOG_DB, 173 dbConfig, model, mutations, rawAccess, this); 174 catalogMap.put(storeName, catalog); 175 success = true; 176 } finally { 177 if (txn != null) { 178 if (success) { 179 txn.commit(); 180 } else { 181 txn.abort(); 182 } 183 } 184 } 185 } 186 } 187 } 188 189 190 mutations = catalog.getMutations(); 191 192 196 model = catalog.getResolvedModel(); 197 198 204 ModelInternal.setCatalog(model, catalog); 205 for (Converter converter : mutations.getConverters()) { 206 converter.getConversion().initialize(model); 207 } 208 } 209 210 public Environment getEnvironment() { 211 return env; 212 } 213 214 public StoreConfig getConfig() { 215 return storeConfig.cloneConfig(); 216 } 217 218 public String getStoreName() { 219 return storeName; 220 } 221 222 public void dumpCatalog() { 223 catalog.dump(); 224 } 225 226 public static Set <String > getStoreNames(Environment env) 227 throws DatabaseException { 228 229 Set <String > set = new HashSet <String >(); 230 for (Object o : env.getDatabaseNames()) { 231 String s = (String ) o; 232 if (s.startsWith(NAME_PREFIX)) { 233 int start = NAME_PREFIX.length(); 234 int end = s.indexOf(NAME_SEPARATOR, start); 235 set.add(s.substring(start, end)); 236 } 237 } 238 return set; 239 } 240 241 public EntityModel getModel() { 242 return model; 243 } 244 245 public Mutations getMutations() { 246 return mutations; 247 } 248 249 257 public synchronized <PK,E> PrimaryIndex<PK,E> 258 getPrimaryIndex(Class <PK> primaryKeyClass, 259 String primaryKeyClassName, 260 Class <E> entityClass, 261 String entityClassName) 262 throws DatabaseException { 263 264 assert (rawAccess && entityClass == RawObject.class) || 265 (!rawAccess && entityClass != RawObject.class); 266 assert (rawAccess && primaryKeyClassName == null) || 267 (!rawAccess && primaryKeyClassName != null); 268 269 checkOpen(); 270 271 PrimaryIndex<PK,E> priIndex = priIndexMap.get(entityClassName); 272 if (priIndex == null) { 273 274 275 EntityMetadata entityMeta = checkEntityClass(entityClassName); 276 PrimaryKeyMetadata priKeyMeta = entityMeta.getPrimaryKey(); 277 if (primaryKeyClassName == null) { 278 primaryKeyClassName = priKeyMeta.getClassName(); 279 } else { 280 String expectClsName = 281 SimpleCatalog.keyClassName(priKeyMeta.getClassName()); 282 if (!primaryKeyClassName.equals(expectClsName)) { 283 throw new IllegalArgumentException 284 ("Wrong primary key class: " + primaryKeyClassName + 285 " Correct class is: " + expectClsName); 286 } 287 } 288 289 290 PersistEntityBinding entityBinding = 291 new PersistEntityBinding(catalog, entityClassName, rawAccess); 292 PersistKeyBinding keyBinding = getKeyBinding(primaryKeyClassName); 293 294 295 String seqName = priKeyMeta.getSequenceName(); 296 if (!storeConfig.getReadOnly() && seqName != null) { 297 entityBinding.keyAssigner = new PersistKeyAssigner 298 (keyBinding, entityBinding, getSequence(seqName)); 299 } 300 301 302 Transaction txn = null; 303 DatabaseConfig dbConfig = getPrimaryConfig(entityMeta); 304 if (dbConfig.getTransactional() && 305 env.getThreadTransaction() == null) { 306 txn = env.beginTransaction(null, null); 307 } 308 boolean success = false; 309 try { 310 311 String dbName = storePrefix + entityClassName; 312 Database db = env.openDatabase(txn, dbName, dbConfig); 313 314 315 priIndex = new PrimaryIndex 316 (db, primaryKeyClass, keyBinding, entityClass, 317 entityBinding); 318 319 320 priIndexMap.put(entityClassName, priIndex); 321 if (DbCompat.getDeferredWrite(dbConfig)) { 322 deferredWriteDatabases.put(db, null); 323 } 324 325 326 if (!dbConfig.getReadOnly()) { 327 openSecondaryIndexes(entityMeta); 328 } 329 success = true; 330 } finally { 331 if (txn != null) { 332 if (success) { 333 txn.commit(); 334 } else { 335 txn.abort(); 336 } 337 } 338 } 339 } 340 return priIndex; 341 } 342 343 348 public synchronized <SK,PK,E1,E2 extends E1> SecondaryIndex<SK,PK,E2> 349 getSecondaryIndex(PrimaryIndex<PK,E1> primaryIndex, 350 Class <E2> entityClass, 351 String entityClassName, 352 Class <SK> keyClass, 353 String keyClassName, 354 String keyName) 355 throws DatabaseException { 356 357 assert (rawAccess && keyClassName == null) || 358 (!rawAccess && keyClassName != null); 359 360 checkOpen(); 361 362 EntityMetadata entityMeta = null; 363 SecondaryKeyMetadata secKeyMeta = null; 364 365 366 if (entityClass != primaryIndex.getEntityClass()) { 367 entityMeta = model.getEntityMetadata(entityClassName); 368 assert entityMeta != null; 369 secKeyMeta = checkSecKey(entityMeta, keyName); 370 String subclassName = entityClass.getName(); 371 String declaringClassName = secKeyMeta.getDeclaringClassName(); 372 if (!subclassName.equals(declaringClassName)) { 373 throw new IllegalArgumentException 374 ("Key for subclass " + subclassName + 375 " is declared in a different class: " + 376 makeSecName(declaringClassName, keyName)); 377 } 378 } 379 380 386 String secName = makeSecName(entityClassName, keyName); 387 SecondaryIndex<SK,PK,E2> secIndex = secIndexMap.get(secName); 388 if (secIndex == null) { 389 if (entityMeta == null) { 390 entityMeta = model.getEntityMetadata(entityClassName); 391 assert entityMeta != null; 392 } 393 if (secKeyMeta == null) { 394 secKeyMeta = checkSecKey(entityMeta, keyName); 395 } 396 397 398 if (keyClassName == null) { 399 keyClassName = getSecKeyClass(secKeyMeta); 400 } else { 401 String expectClsName = getSecKeyClass(secKeyMeta); 402 if (!keyClassName.equals(expectClsName)) { 403 throw new IllegalArgumentException 404 ("Wrong secondary key class: " + keyClassName + 405 " Correct class is: " + expectClsName); 406 } 407 } 408 409 secIndex = openSecondaryIndex 410 (null, primaryIndex, entityClass, entityMeta, 411 keyClass, keyClassName, secKeyMeta, secName); 412 } 413 return secIndex; 414 } 415 416 423 synchronized void openSecondaryIndexes(EntityMetadata entityMeta) 424 throws DatabaseException { 425 426 String entityClassName = entityMeta.getClassName(); 427 PrimaryIndex<Object ,Object > priIndex = 428 priIndexMap.get(entityClassName); 429 assert priIndex != null; 430 Class <Object > entityClass = priIndex.getEntityClass(); 431 432 for (SecondaryKeyMetadata secKeyMeta : 433 entityMeta.getSecondaryKeys().values()) { 434 String keyName = secKeyMeta.getKeyName(); 435 String secName = makeSecName(entityClassName, keyName); 436 SecondaryIndex<Object ,Object ,Object > secIndex = 437 secIndexMap.get(secName); 438 if (secIndex == null) { 439 String keyClassName = getSecKeyClass(secKeyMeta); 440 441 Class keyClass = 442 SimpleCatalog.keyClassForName(keyClassName); 443 openSecondaryIndex 444 (null, priIndex, entityClass, entityMeta, 445 keyClass, keyClassName, secKeyMeta, 446 makeSecName 447 (entityClassName, secKeyMeta.getKeyName())); 448 } 449 } 450 } 451 452 456 private <SK,PK,E1,E2 extends E1> SecondaryIndex<SK,PK,E2> 457 openSecondaryIndex(Transaction txn, 458 PrimaryIndex<PK,E1> primaryIndex, 459 Class <E2> entityClass, 460 EntityMetadata entityMeta, 461 Class <SK> keyClass, 462 String keyClassName, 463 SecondaryKeyMetadata secKeyMeta, 464 String secName) 465 throws DatabaseException { 466 467 assert !secIndexMap.containsKey(secName); 468 String dbName = storePrefix + secName; 469 SecondaryConfig config = 470 getSecondaryConfig(secName, entityMeta, keyClassName, secKeyMeta); 471 Database priDb = primaryIndex.getDatabase(); 472 DatabaseConfig priConfig = priDb.getConfig(); 473 474 String relatedClsName = secKeyMeta.getRelatedEntity(); 475 if (relatedClsName != null) { 476 PrimaryIndex relatedIndex = priIndexMap.get(relatedClsName); 477 if (relatedIndex == null) { 478 EntityMetadata relatedEntityMeta = 479 checkEntityClass(relatedClsName); 480 Class relatedKeyCls; 481 String relatedKeyClsName; 482 Class relatedCls; 483 if (rawAccess) { 484 relatedCls = RawObject.class; 485 relatedKeyCls = Object .class; 486 relatedKeyClsName = null; 487 } else { 488 try { 489 relatedCls = Class.forName(relatedClsName); 490 } catch (ClassNotFoundException e) { 491 throw new IllegalArgumentException 492 ("Foreign key database class not found: " + 493 relatedClsName); 494 } 495 relatedKeyClsName = SimpleCatalog.keyClassName 496 (relatedEntityMeta.getPrimaryKey().getClassName()); 497 relatedKeyCls = 498 SimpleCatalog.keyClassForName(relatedKeyClsName); 499 } 500 501 relatedIndex = getPrimaryIndex 502 (relatedKeyCls, relatedKeyClsName, 503 relatedCls, relatedClsName); 504 } 505 config.setForeignKeyDatabase(relatedIndex.getDatabase()); 506 } 507 508 if (config.getTransactional() != priConfig.getTransactional() || 509 DbCompat.getDeferredWrite(config) != 510 DbCompat.getDeferredWrite(priConfig) || 511 config.getReadOnly() != priConfig.getReadOnly()) { 512 throw new IllegalArgumentException 513 ("One of these properties was changed to be inconsistent" + 514 " with the associated primary database: " + 515 " Transactional, DeferredWrite, ReadOnly"); 516 } 517 518 PersistKeyBinding keyBinding = getKeyBinding(keyClassName); 519 520 SecondaryDatabase db = 521 env.openSecondaryDatabase(txn, dbName, priDb, config); 522 SecondaryIndex<SK,PK,E2> secIndex = new SecondaryIndex 523 (db, null, primaryIndex, keyClass, keyBinding); 524 525 526 secIndexMap.put(secName, secIndex); 527 if (DbCompat.getDeferredWrite(config)) { 528 deferredWriteDatabases.put(db, null); 529 } 530 return secIndex; 531 } 532 533 public void sync() 534 throws DatabaseException { 535 536 List <Database> dbs = new ArrayList <Database>(); 537 synchronized (this) { 538 dbs.addAll(deferredWriteDatabases.keySet()); 539 } 540 int nDbs = dbs.size(); 541 if (nDbs > 0) { 542 for (int i = 0; i < nDbs; i += 1) { 543 Database db = dbs.get(i); 544 boolean flushLog = (i == nDbs - 1); 545 DbCompat.syncDeferredWrite(db, flushLog); 546 547 if (syncHook != null) { 548 syncHook.onSync(db, flushLog); 549 } 550 } 551 } 552 } 553 554 public void truncateClass(Class entityClass) 555 throws DatabaseException { 556 557 truncateClass(null, entityClass); 558 } 559 560 public synchronized void truncateClass(Transaction txn, Class entityClass) 561 throws DatabaseException { 562 563 checkOpen(); 564 565 566 closeClass(entityClass); 567 568 String clsName = entityClass.getName(); 569 EntityMetadata entityMeta = checkEntityClass(clsName); 570 571 576 String dbName = storePrefix + clsName; 577 boolean primaryExists = true; 578 try { 579 env.truncateDatabase(txn, dbName, false); 580 } catch (DatabaseNotFoundException ignored) { 581 primaryExists = false; 582 } 583 if (primaryExists) { 584 DatabaseException firstException = null; 585 for (SecondaryKeyMetadata keyMeta : 586 entityMeta.getSecondaryKeys().values()) { 587 try { 588 env.truncateDatabase 589 (txn, 590 storePrefix + 591 makeSecName(clsName, keyMeta.getKeyName()), 592 false); 593 } catch (DatabaseNotFoundException ignored) { 594 595 } catch (DatabaseException e) { 596 if (firstException == null) { 597 firstException = e; 598 } 599 } 600 } 601 if (firstException != null) { 602 throw firstException; 603 } 604 } 605 } 606 607 public synchronized void closeClass(Class entityClass) 608 throws DatabaseException { 609 610 checkOpen(); 611 String clsName = entityClass.getName(); 612 EntityMetadata entityMeta = checkEntityClass(clsName); 613 614 PrimaryIndex priIndex = priIndexMap.get(clsName); 615 if (priIndex != null) { 616 617 DatabaseException firstException = null; 618 for (SecondaryKeyMetadata keyMeta : 619 entityMeta.getSecondaryKeys().values()) { 620 621 String secName = makeSecName(clsName, keyMeta.getKeyName()); 622 SecondaryIndex secIndex = secIndexMap.get(secName); 623 if (secIndex != null) { 624 Database db = secIndex.getDatabase(); 625 firstException = closeDb(db, firstException); 626 firstException = 627 closeDb(secIndex.getKeysDatabase(), firstException); 628 secIndexMap.remove(secName); 629 deferredWriteDatabases.remove(db); 630 } 631 } 632 633 Database db = priIndex.getDatabase(); 634 firstException = closeDb(db, firstException); 635 priIndexMap.remove(clsName); 636 deferredWriteDatabases.remove(db); 637 638 639 if (firstException != null) { 640 throw firstException; 641 } 642 } 643 } 644 645 public synchronized void close() 646 throws DatabaseException { 647 648 checkOpen(); 649 DatabaseException firstException = null; 650 try { 651 if (rawAccess) { 652 boolean allClosed = catalog.close(); 653 assert allClosed; 654 } else { 655 synchronized (catalogPool) { 656 Map <String ,PersistCatalog> catalogMap = 657 catalogPool.get(env); 658 assert catalogMap != null; 659 if (catalog.close()) { 660 661 catalogMap.remove(storeName); 662 } 663 } 664 } 665 catalog = null; 666 } catch (DatabaseException e) { 667 if (firstException == null) { 668 firstException = e; 669 } 670 } 671 firstException = closeDb(sequenceDb, firstException); 672 for (SecondaryIndex index : secIndexMap.values()) { 673 firstException = closeDb(index.getDatabase(), firstException); 674 firstException = closeDb(index.getKeysDatabase(), firstException); 675 } 676 for (PrimaryIndex index : priIndexMap.values()) { 677 firstException = closeDb(index.getDatabase(), firstException); 678 } 679 if (firstException != null) { 680 throw firstException; 681 } 682 } 683 684 public synchronized Sequence getSequence(String name) 685 throws DatabaseException { 686 687 checkOpen(); 688 689 if (storeConfig.getReadOnly()) { 690 throw new IllegalStateException ("Store is read-only"); 691 } 692 693 Sequence seq = sequenceMap.get(name); 694 if (seq == null) { 695 if (sequenceDb == null) { 696 String dbName = storePrefix + SEQUENCE_DB; 697 DatabaseConfig dbConfig = new DatabaseConfig(); 698 dbConfig.setTransactional(storeConfig.getTransactional()); 699 dbConfig.setAllowCreate(true); 700 sequenceDb = env.openDatabase(null, dbName, dbConfig); 701 } 702 DatabaseEntry entry = new DatabaseEntry(); 703 StringBinding.stringToEntry(name, entry); 704 seq = sequenceDb.openSequence(null, entry, getSequenceConfig(name)); 705 sequenceMap.put(name, seq); 706 } 707 return seq; 708 } 709 710 public synchronized SequenceConfig getSequenceConfig(String name) { 711 checkOpen(); 712 SequenceConfig config = sequenceConfigMap.get(name); 713 if (config == null) { 714 config = new SequenceConfig(); 715 config.setInitialValue(1); 716 config.setRange(1, Long.MAX_VALUE); 717 config.setCacheSize(100); 718 config.setAutoCommitNoSync(true); 719 config.setAllowCreate(!storeConfig.getReadOnly()); 720 sequenceConfigMap.put(name, config); 721 } 722 return config; 723 } 724 725 public synchronized void setSequenceConfig(String name, 726 SequenceConfig config) { 727 checkOpen(); 728 sequenceConfigMap.put(name, config); 729 } 730 731 public synchronized DatabaseConfig getPrimaryConfig(Class entityClass) { 732 checkOpen(); 733 String clsName = entityClass.getName(); 734 EntityMetadata meta = checkEntityClass(clsName); 735 return getPrimaryConfig(meta).cloneConfig(); 736 } 737 738 private synchronized DatabaseConfig getPrimaryConfig(EntityMetadata meta) { 739 String clsName = meta.getClassName(); 740 DatabaseConfig config = priConfigMap.get(clsName); 741 if (config == null) { 742 config = new DatabaseConfig(); 743 config.setTransactional(storeConfig.getTransactional()); 744 config.setAllowCreate(!storeConfig.getReadOnly()); 745 config.setReadOnly(storeConfig.getReadOnly()); 746 DbCompat.setDeferredWrite(config, storeConfig.getDeferredWrite()); 747 setBtreeComparator(config, meta.getPrimaryKey().getClassName()); 748 priConfigMap.put(clsName, config); 749 } 750 return config; 751 } 752 753 public synchronized void setPrimaryConfig(Class entityClass, 754 DatabaseConfig config) { 755 checkOpen(); 756 String clsName = entityClass.getName(); 757 if (priIndexMap.containsKey(clsName)) { 758 throw new IllegalStateException 759 ("Cannot set config after DB is open"); 760 } 761 EntityMetadata meta = checkEntityClass(clsName); 762 DatabaseConfig dbConfig = getPrimaryConfig(meta); 763 if (config.getSortedDuplicates() || 764 config.getBtreeComparator() != dbConfig.getBtreeComparator()) { 765 throw new IllegalArgumentException 766 ("One of these properties was illegally changed: " + 767 " SortedDuplicates or BtreeComparator"); 768 } 769 priConfigMap.put(clsName, config); 770 } 771 772 public synchronized SecondaryConfig getSecondaryConfig(Class entityClass, 773 String keyName) { 774 checkOpen(); 775 String entityClsName = entityClass.getName(); 776 EntityMetadata entityMeta = checkEntityClass(entityClsName); 777 SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta, keyName); 778 String keyClassName = getSecKeyClass(secKeyMeta); 779 String secName = makeSecName(entityClass.getName(), keyName); 780 return (SecondaryConfig) getSecondaryConfig 781 (secName, entityMeta, keyClassName, secKeyMeta).cloneConfig(); 782 } 783 784 private SecondaryConfig getSecondaryConfig(String secName, 785 EntityMetadata entityMeta, 786 String keyClassName, 787 SecondaryKeyMetadata 788 secKeyMeta) { 789 SecondaryConfig config = secConfigMap.get(secName); 790 if (config == null) { 791 792 DatabaseConfig priConfig = getPrimaryConfig(entityMeta); 793 config = new SecondaryConfig(); 794 config.setTransactional(priConfig.getTransactional()); 795 config.setAllowCreate(!priConfig.getReadOnly()); 796 config.setReadOnly(priConfig.getReadOnly()); 797 DbCompat.setDeferredWrite 798 (config, DbCompat.getDeferredWrite(priConfig)); 799 800 config.setAllowPopulate(true); 801 Relationship rel = secKeyMeta.getRelationship(); 802 config.setSortedDuplicates(rel == Relationship.MANY_TO_ONE || 803 rel == Relationship.MANY_TO_MANY); 804 setBtreeComparator(config, secKeyMeta.getClassName()); 805 PersistKeyCreator keyCreator = new PersistKeyCreator 806 (catalog, entityMeta, keyClassName, secKeyMeta); 807 if (rel == Relationship.ONE_TO_MANY || 808 rel == Relationship.MANY_TO_MANY) { 809 config.setMultiKeyCreator(keyCreator); 810 } else { 811 config.setKeyCreator(keyCreator); 812 } 813 DeleteAction deleteAction = secKeyMeta.getDeleteAction(); 814 if (deleteAction != null) { 815 ForeignKeyDeleteAction baseDeleteAction; 816 switch (deleteAction) { 817 case ABORT: 818 baseDeleteAction = ForeignKeyDeleteAction.ABORT; 819 break; 820 case CASCADE: 821 baseDeleteAction = ForeignKeyDeleteAction.CASCADE; 822 break; 823 case NULLIFY: 824 baseDeleteAction = ForeignKeyDeleteAction.NULLIFY; 825 break; 826 default: 827 throw new IllegalStateException (deleteAction.toString()); 828 } 829 config.setForeignKeyDeleteAction(baseDeleteAction); 830 if (deleteAction == DeleteAction.NULLIFY) { 831 config.setForeignMultiKeyNullifier(keyCreator); 832 } 833 } 834 secConfigMap.put(secName, config); 835 } 836 return config; 837 } 838 839 public synchronized void setSecondaryConfig(Class entityClass, 840 String keyName, 841 SecondaryConfig config) { 842 checkOpen(); 843 String entityClsName = entityClass.getName(); 844 EntityMetadata entityMeta = checkEntityClass(entityClsName); 845 SecondaryKeyMetadata secKeyMeta = checkSecKey(entityMeta, keyName); 846 String keyClassName = getSecKeyClass(secKeyMeta); 847 String secName = makeSecName(entityClass.getName(), keyName); 848 if (secIndexMap.containsKey(secName)) { 849 throw new IllegalStateException 850 ("Cannot set config after DB is open"); 851 } 852 SecondaryConfig dbConfig = 853 getSecondaryConfig(secName, entityMeta, keyClassName, secKeyMeta); 854 if (config.getSortedDuplicates() != dbConfig.getSortedDuplicates() || 855 config.getBtreeComparator() != dbConfig.getBtreeComparator() || 856 config.getDuplicateComparator() != null || 857 config.getAllowPopulate() != dbConfig.getAllowPopulate() || 858 config.getKeyCreator() != dbConfig.getKeyCreator() || 859 config.getMultiKeyCreator() != dbConfig.getMultiKeyCreator() || 860 config.getForeignKeyNullifier() != 861 dbConfig.getForeignKeyNullifier() || 862 config.getForeignMultiKeyNullifier() != 863 dbConfig.getForeignMultiKeyNullifier() || 864 config.getForeignKeyDeleteAction() != 865 dbConfig.getForeignKeyDeleteAction() || 866 config.getForeignKeyDatabase() != null) { 867 throw new IllegalArgumentException 868 ("One of these properties was illegally changed: " + 869 " SortedDuplicates, BtreeComparator, DuplicateComparator," + 870 " AllowPopulate, KeyCreator, MultiKeyCreator," + 871 " ForeignKeyNullifer, ForeignMultiKeyNullifier," + 872 " ForeignKeyDeleteAction, ForeignKeyDatabase"); 873 } 874 secConfigMap.put(secName, config); 875 } 876 877 private static String makeSecName(String entityClsName, String keyName) { 878 return entityClsName + NAME_SEPARATOR + keyName; 879 } 880 881 static String makePriDbName(String storePrefix, String entityClsName) { 882 return storePrefix + entityClsName; 883 } 884 885 static String makeSecDbName(String storePrefix, 886 String entityClsName, 887 String keyName) { 888 return storePrefix + makeSecName(entityClsName, keyName); 889 } 890 891 private void checkOpen() { 892 if (catalog == null) { 893 throw new IllegalStateException ("Store has been closed"); 894 } 895 } 896 897 private EntityMetadata checkEntityClass(String clsName) { 898 EntityMetadata meta = model.getEntityMetadata(clsName); 899 if (meta == null) { 900 throw new IllegalArgumentException 901 ("Not an entity class: " + clsName); 902 } 903 return meta; 904 } 905 906 private SecondaryKeyMetadata checkSecKey(EntityMetadata entityMeta, 907 String keyName) { 908 SecondaryKeyMetadata secKeyMeta = 909 entityMeta.getSecondaryKeys().get(keyName); 910 if (secKeyMeta == null) { 911 throw new IllegalArgumentException 912 ("Not a secondary key: " + 913 makeSecName(entityMeta.getClassName(), keyName)); 914 } 915 return secKeyMeta; 916 } 917 918 private String getSecKeyClass(SecondaryKeyMetadata secKeyMeta) { 919 String clsName = secKeyMeta.getElementClassName(); 920 if (clsName == null) { 921 clsName = secKeyMeta.getClassName(); 922 } 923 return SimpleCatalog.keyClassName(clsName); 924 } 925 926 private PersistKeyBinding getKeyBinding(String keyClassName) { 927 PersistKeyBinding binding = keyBindingMap.get(keyClassName); 928 if (binding == null) { 929 binding = new PersistKeyBinding(catalog, keyClassName, rawAccess); 930 keyBindingMap.put(keyClassName, binding); 931 } 932 return binding; 933 } 934 935 private void setBtreeComparator(DatabaseConfig config, String clsName) { 936 if (!rawAccess) { 937 ClassMetadata meta = model.getClassMetadata(clsName); 938 if (meta != null) { 939 List <FieldMetadata> compositeKeyFields = 940 meta.getCompositeKeyFields(); 941 if (compositeKeyFields != null) { 942 Class keyClass = SimpleCatalog.keyClassForName(clsName); 943 if (Comparable .class.isAssignableFrom(keyClass)) { 944 Comparator <Object > cmp = new PersistComparator 945 (clsName, compositeKeyFields, 946 getKeyBinding(clsName)); 947 config.setBtreeComparator(cmp); 948 } 949 } 950 } 951 } 952 } 953 954 private DatabaseException closeDb(Database db, 955 DatabaseException firstException) { 956 if (db != null) { 957 try { 958 db.close(); 959 } catch (DatabaseException e) { 960 if (firstException == null) { 961 firstException = e; 962 } 963 } 964 } 965 return firstException; 966 } 967 968 public EvolveStats evolve(EvolveConfig config) 969 throws DatabaseException { 970 971 checkOpen(); 972 List <Format> toEvolve = new ArrayList <Format>(); 973 Set <String > configToEvolve = config.getClassesToEvolve(); 974 if (configToEvolve.isEmpty()) { 975 catalog.getEntityFormats(toEvolve); 976 } else { 977 for (String name : configToEvolve) { 978 Format format = catalog.getFormat(name); 979 if (format == null) { 980 throw new IllegalArgumentException 981 ("Class to evolve is not persistent: " + name); 982 } 983 if (!format.isEntity()) { 984 throw new IllegalArgumentException 985 ("Class to evolve is not an entity class: " + name); 986 } 987 toEvolve.add(format); 988 } 989 } 990 991 EvolveEvent event = EvolveInternal.newEvent(); 992 for (Format format : toEvolve) { 993 if (format.getEvolveNeeded()) { 994 evolveIndex(format, event, config.getEvolveListener()); 995 format.setEvolveNeeded(false); 996 catalog.flush(); 997 } 998 } 999 1000 return event.getStats(); 1001 } 1002 1003 private void evolveIndex(Format format, 1004 EvolveEvent event, 1005 EvolveListener listener) 1006 throws DatabaseException { 1007 1008 Class entityClass = format.getType(); 1009 String entityClassName = format.getClassName(); 1010 EntityMetadata meta = model.getEntityMetadata(entityClassName); 1011 String keyClassName = meta.getPrimaryKey().getClassName(); 1012 keyClassName = SimpleCatalog.keyClassName(keyClassName); 1013 DatabaseConfig dbConfig = getPrimaryConfig(meta); 1014 1015 PrimaryIndex<Object ,Object > index = getPrimaryIndex 1016 (Object .class, keyClassName, entityClass, entityClassName); 1017 Database db = index.getDatabase(); 1018 1019 EntityBinding binding = index.getEntityBinding(); 1020 DatabaseEntry key = new DatabaseEntry(); 1021 DatabaseEntry data = new DatabaseEntry(); 1022 1023 Cursor readCursor = db.openCursor(null, CursorConfig.READ_UNCOMMITTED); 1024 try { 1025 while (readCursor.getNext(key, data, null) == 1026 OperationStatus.SUCCESS) { 1027 if (evolveNeeded(key, data, binding)) { 1028 Transaction txn = null; 1029 if (dbConfig.getTransactional()) { 1030 boolean success = false; 1031 txn = env.beginTransaction(null, null); 1032 } 1033 boolean doCommit = false; 1034 Cursor writeCursor = null; 1035 try { 1036 writeCursor = db.openCursor(txn, null); 1037 if (writeCursor.getSearchKey 1038 (key, data, LockMode.RMW) == 1039 OperationStatus.SUCCESS) { 1040 boolean written = false; 1041 if (evolveNeeded(key, data, binding)) { 1042 writeCursor.putCurrent(data); 1043 written = true; 1044 } 1045 if (listener != null) { 1046 EvolveInternal.updateEvent 1047 (event, entityClassName, 1, 1048 written ? 1 : 0); 1049 if (!listener.evolveProgress(event)) { 1050 break; 1051 } 1052 } 1053 } 1054 } finally { 1055 if (writeCursor != null) { 1056 writeCursor.close(); 1057 } 1058 if (txn != null) { 1059 if (doCommit) { 1060 txn.commit(); 1061 } else { 1062 txn.abort(); 1063 } 1064 } 1065 } 1066 } 1067 } 1068 } finally { 1069 readCursor.close(); 1070 } 1071 } 1072 1073 1077 private boolean evolveNeeded(DatabaseEntry key, 1078 DatabaseEntry data, 1079 EntityBinding binding) { 1080 Object entity = binding.entryToObject(key, data); 1081 DatabaseEntry newData = new DatabaseEntry(); 1082 binding.objectToData(entity, newData); 1083 if (data.equals(newData)) { 1084 return false; 1085 } else { 1086 byte[] bytes = newData.getData(); 1087 int off = newData.getOffset(); 1088 int size = newData.getSize(); 1089 data.setData(bytes, off, size); 1090 return true; 1091 } 1092 } 1093 1094 1097 public static void setSyncHook(SyncHook hook) { 1098 syncHook = hook; 1099 } 1100 1101 1104 public interface SyncHook { 1105 void onSync(Database db, boolean flushLog); 1106 } 1107} 1108 | Popular Tags |