1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002,2008 Oracle. All rights reserved. 5 * 6 * $Id: PersistCatalog.java,v 1.2 2008/02/08 20:12:37 mark Exp $ 7 */ 8 9package com.sleepycat.persist.impl; 10 11import java.io.ByteArrayInputStream; 12import java.io.ByteArrayOutputStream; 13import java.io.FileNotFoundException; 14import java.io.IOException; 15import java.io.ObjectInputStream; 16import java.io.ObjectOutputStream; 17import java.io.Serializable; 18import java.util.ArrayList; 19import java.util.Collection; 20import java.util.HashMap; 21import java.util.HashSet; 22import java.util.IdentityHashMap; 23import java.util.List; 24import java.util.Map; 25import java.util.NoSuchElementException; 26import java.util.Set; 27 28import com.sleepycat.bind.tuple.IntegerBinding; 29import com.sleepycat.compat.DbCompat; 30import com.sleepycat.db.Database; 31import com.sleepycat.db.DatabaseConfig; 32import com.sleepycat.db.DatabaseEntry; 33import com.sleepycat.db.DatabaseException; 34import com.sleepycat.db.Environment; 35import com.sleepycat.db.OperationStatus; 36import com.sleepycat.db.Transaction; 37import com.sleepycat.persist.DatabaseNamer; 38import com.sleepycat.persist.evolve.DeletedClassException; 39import com.sleepycat.persist.evolve.IncompatibleClassException; 40import com.sleepycat.persist.evolve.Mutations; 41import com.sleepycat.persist.evolve.Renamer; 42import com.sleepycat.persist.model.AnnotationModel; 43import com.sleepycat.persist.model.ClassMetadata; 44import com.sleepycat.persist.model.EntityMetadata; 45import com.sleepycat.persist.model.EntityModel; 46import com.sleepycat.persist.raw.RawObject; 47import com.sleepycat.util.RuntimeExceptionWrapper; 48 49/** 50 * The catalog of class formats for a store, along with its associated model 51 * and mutations. 52 * 53 * @author Mark Hayes 54 */ 55public class PersistCatalog implements Catalog { 56 57 /** 58 * Key to Data record in the catalog database. In the JE 3.0.12 beta 59 * version the formatList record is stored under this key and is converted 60 * to a Data object when it is read. 61 */ 62 private static final byte[] DATA_KEY = getIntBytes(-1); 63 64 /** 65 * Key to a JE 3.0.12 beta version mutations record in the catalog 66 * database. This record is no longer used because mutations are stored in 67 * the Data record and is deleted when the beta version is detected. 68 */ 69 private static final byte[] BETA_MUTATIONS_KEY = getIntBytes(-2); 70 71 private static byte[] getIntBytes(int val) { 72 DatabaseEntry entry = new DatabaseEntry(); 73 IntegerBinding.intToEntry(val, entry); 74 assert entry.getSize() == 4 && entry.getData().length == 4; 75 return entry.getData(); 76 } 77 78 /** 79 * Used by unit tests. 80 */ 81 public static boolean expectNoClassChanges; 82 public static boolean unevolvedFormatsEncountered; 83 84 /** 85 * The object stored under DATA_KEY in the catalog database. 86 */ 87 private static class Data implements Serializable { 88 89 static final long serialVersionUID = 7515058069137413261L; 90 91 List<Format> formatList; 92 Mutations mutations; 93 int version; 94 } 95 96 /** 97 * A list of all formats indexed by formatId. Element zero is unused and 98 * null, since IDs start at one; this avoids adjusting the ID to index the 99 * list. Some elements are null to account for predefined IDs that are not 100 * used. 101 * 102 * <p>This field, like formatMap, is volatile because it is reassigned 103 * when dynamically adding new formats. See {@link getFormat(Class)}.</p> 104 */ 105 private volatile List<Format> formatList; 106 107 /** 108 * A map of the current/live formats in formatList, indexed by class name. 109 * 110 * <p>This field, like formatList, is volatile because it is reassigned 111 * when dynamically adding new formats. See {@link getFormat(Class)}.</p> 112 */ 113 private volatile Map<String,Format> formatMap; 114 115 /** 116 * A map of the latest formats (includes deleted formats) in formatList, 117 * indexed by class name. 118 * 119 * <p>This field, like formatMap, is volatile because it is reassigned 120 * when dynamically adding new formats. See {@link getFormat(Class)}.</p> 121 */ 122 private volatile Map<String,Format> latestFormatMap; 123 124 /** 125 * A temporary map of proxied class name to proxy class name. Used during 126 * catalog creation, and then set to null. This map is used to force proxy 127 * formats to be created prior to proxied formats. [#14665] 128 */ 129 private Map<String,String> proxyClassMap; 130 131 private boolean rawAccess; 132 private EntityModel model; 133 private Mutations mutations; 134 private Database db; 135 private int openCount; 136 137 /** 138 * The Store is normally present but may be null in unit tests (for 139 * example, BindingTest). 140 */ 141 private Store store; 142 143 /** 144 * The Evolver and catalog Data are non-null during catalog initialization, 145 * and null otherwise. 146 */ 147 private Evolver evolver; 148 private Data catalogData; 149 150 /** 151 * Creates a new catalog, opening the database and reading it from a given 152 * catalog database if it already exists. All predefined formats and 153 * formats for the given model are added. For modified classes, old 154 * formats are defined based on the rules for compatible class changes and 155 * the given mutations. If any format is changed or added, and the 156 * database is not read-only, write the initialized catalog to the 157 * database. 158 */ 159 public PersistCatalog(Transaction txn, 160 Environment env, 161 String storePrefix, 162 String dbName, 163 DatabaseConfig dbConfig, 164 EntityModel modelParam, 165 Mutations mutationsParam, 166 boolean rawAccess, 167 Store store) 168 throws DatabaseException { 169 170 this.rawAccess = rawAccess; 171 this.store = store; 172 /* store may be null for testing. */ 173 String[] fileAndDbNames = (store != null) ? 174 store.parseDbName(dbName) : 175 Store.parseDbName(dbName, DatabaseNamer.DEFAULT); 176 try { 177 db = DbCompat.openDatabase 178 (env, txn, fileAndDbNames[0], fileAndDbNames[1], 179 dbConfig); 180 } catch (FileNotFoundException e) { 181 throw new DatabaseException(e); 182 } 183 openCount = 1; 184 boolean success = false; 185 try { 186 catalogData = readData(txn); 187 mutations = catalogData.mutations; 188 if (mutations == null) { 189 mutations = new Mutations(); 190 } 191 192 /* 193 * When the beta version is detected, force a re-write of the 194 * catalog and disallow class changes. This brings the catalog up 195 * to date so that evolution can proceed correctly from then on. 196 */ 197 boolean betaVersion = (catalogData.version == BETA_VERSION); 198 boolean forceWriteData = betaVersion; 199 boolean disallowClassChanges = betaVersion; 200 201 /* 202 * Store the given mutations if they are different from the stored 203 * mutations, and force evolution to apply the new mutations. 204 */ 205 boolean forceEvolution = false; 206 if (mutationsParam != null && 207 !mutations.equals(mutationsParam)) { 208 mutations = mutationsParam; 209 forceWriteData = true; 210 forceEvolution = true; 211 } 212 213 /* Get the existing format list, or copy it from SimpleCatalog. */ 214 formatList = catalogData.formatList; 215 if (formatList == null) { 216 formatList = SimpleCatalog.copyFormatList(); 217 218 /* 219 * Special cases: Object and Number are predefined but are not 220 * simple types. 221 */ 222 Format format = new NonPersistentFormat(Object.class); 223 format.setId(Format.ID_OBJECT); 224 formatList.set(Format.ID_OBJECT, format); 225 format = new NonPersistentFormat(Number.class); 226 format.setId(Format.ID_NUMBER); 227 formatList.set(Format.ID_NUMBER, format); 228 } else { 229 if (SimpleCatalog.copyMissingFormats(formatList)) { 230 forceWriteData = true; 231 } 232 } 233 234 /* Special handling for JE 3.0.12 beta formats. */ 235 if (betaVersion) { 236 Map<String,Format> formatMap = new HashMap<String,Format>(); 237 for (Format format : formatList) { 238 if (format != null) { 239 formatMap.put(format.getClassName(), format); 240 } 241 } 242 for (Format format : formatList) { 243 if (format != null) { 244 format.migrateFromBeta(formatMap); 245 } 246 } 247 } 248 249 /* 250 * If we should not use the current model, initialize the stored 251 * model and return. 252 */ 253 formatMap = new HashMap<String,Format>(formatList.size()); 254 latestFormatMap = new HashMap<String,Format>(formatList.size()); 255 if (rawAccess) { 256 for (Format format : formatList) { 257 if (format != null) { 258 String name = format.getClassName(); 259 if (format.isCurrentVersion()) { 260 formatMap.put(name, format); 261 } 262 if (format == format.getLatestVersion()) { 263 latestFormatMap.put(name, format); 264 } 265 } 266 } 267 for (Format format : formatList) { 268 if (format != null) { 269 format.initializeIfNeeded(this); 270 } 271 } 272 model = new StoredModel(this); 273 success = true; 274 return; 275 } 276 277 /* 278 * We are opening a store that uses the current model. Default to 279 * the AnnotationModel if no model is specified. 280 */ 281 if (modelParam != null) { 282 model = modelParam; 283 } else { 284 model = new AnnotationModel(); 285 } 286 287 /* 288 * Add all predefined (simple) formats to the format map. The 289 * current version of other formats will be added below. 290 */ 291 for (int i = 0; i <= Format.ID_PREDEFINED; i += 1) { 292 Format simpleFormat = formatList.get(i); 293 if (simpleFormat != null) { 294 formatMap.put(simpleFormat.getClassName(), simpleFormat); 295 } 296 } 297 298 /* 299 * Known classes are those explicitly registered by the user via 300 * the model, plus the predefined proxy classes. 301 */ 302 List<String> knownClasses = 303 new ArrayList<String>(model.getKnownClasses()); 304 addPredefinedProxies(knownClasses); 305 306 /* 307 * Create a temporary map of proxied class name to proxy class 308 * name, using all known formats and classes. This map is used to 309 * force proxy formats to be created prior to proxied formats. 310 * [#14665] 311 */ 312 proxyClassMap = new HashMap<String,String>(); 313 for (Format oldFormat : formatList) { 314 if (oldFormat == null || Format.isPredefined(oldFormat)) { 315 continue; 316 } 317 String oldName = oldFormat.getClassName(); 318 Renamer renamer = mutations.getRenamer 319 (oldName, oldFormat.getVersion(), null); 320 String newName = 321 (renamer != null) ? renamer.getNewName() : oldName; 322 addProxiedClass(newName); 323 } 324 for (String className : knownClasses) { 325 addProxiedClass(className); 326 } 327 328 /* 329 * Add known formats from the model and the predefined proxies. 330 * In general, classes will not be present in an AnnotationModel 331 * until an instance is stored, in which case an old format exists. 332 * However, registered proxy classes are an exception and must be 333 * added in advance. And the user may choose to register new 334 * classes in advance. The more formats we define in advance, the 335 * less times we have to write to the catalog database. 336 */ 337 Map<String,Format> newFormats = new HashMap<String,Format>(); 338 for (String className : knownClasses) { 339 createFormat(className, newFormats); 340 } 341 342 /* 343 * Perform class evolution for all old formats, and throw an 344 * exception that contains the messages for all of the errors in 345 * mutations or in the definition of new classes. 346 */ 347 evolver = new Evolver 348 (this, storePrefix, mutations, newFormats, forceEvolution, 349 disallowClassChanges); 350 for (Format oldFormat : formatList) { 351 if (oldFormat == null || Format.isPredefined(oldFormat)) { 352 continue; 353 } 354 if (oldFormat.isEntity()) { 355 evolver.evolveFormat(oldFormat); 356 } else { 357 evolver.addNonEntityFormat(oldFormat); 358 } 359 } 360 evolver.finishEvolution(); 361 String errors = evolver.getErrors(); 362 if (errors != null) { 363 throw new IncompatibleClassException(errors); 364 } 365 366 /* 367 * Add the new formats remaining. New formats that are equal to 368 * old formats were removed from the newFormats map above. 369 */ 370 for (Format newFormat : newFormats.values()) { 371 addFormat(newFormat); 372 } 373 374 /* Initialize all formats. */ 375 for (Format format : formatList) { 376 if (format != null) { 377 format.initializeIfNeeded(this); 378 if (format == format.getLatestVersion()) { 379 latestFormatMap.put(format.getClassName(), format); 380 } 381 } 382 } 383 384 boolean needWrite = 385 newFormats.size() > 0 || 386 evolver.areFormatsChanged(); 387 388 /* For unit testing. */ 389 if (expectNoClassChanges && needWrite) { 390 throw new IllegalStateException 391 ("Unexpected changes " + 392 " newFormats.size=" + newFormats.size() + 393 " areFormatsChanged=" + evolver.areFormatsChanged()); 394 } 395 396 /* Write the catalog if anything changed. */ 397 if ((needWrite || forceWriteData) && 398 !db.getConfig().getReadOnly()) { 399 400 /* 401 * Only rename/remove databases if we are going to update the 402 * catalog to reflect those class changes. 403 */ 404 evolver.renameAndRemoveDatabases(store, txn); 405 406 /* 407 * Note that we use the Data object that was read above, and 408 * the beta version determines whether to delete the old 409 * mutations record. 410 */ 411 catalogData.formatList = formatList; 412 catalogData.mutations = mutations; 413 writeData(txn, catalogData); 414 } else if (forceWriteData) { 415 throw new IllegalArgumentException 416 ("When an upgrade is required the store may not be " + 417 "opened read-only"); 418 } 419 420 success = true; 421 } finally { 422 423 /* 424 * Fields needed only for the duration of this ctor and which 425 * should be null afterwards. 426 */ 427 proxyClassMap = null; 428 catalogData = null; 429 evolver = null; 430 431 if (!success) { 432 close(); 433 } 434 } 435 } 436 437 public void getEntityFormats(Collection<Format> entityFormats) { 438 for (Format format : formatMap.values()) { 439 if (format.isEntity()) { 440 entityFormats.add(format); 441 } 442 } 443 } 444 445 private void addProxiedClass(String className) { 446 ClassMetadata metadata = model.getClassMetadata(className); 447 if (metadata != null) { 448 String proxiedClassName = metadata.getProxiedClassName(); 449 if (proxiedClassName != null) { 450 proxyClassMap.put(proxiedClassName, className); 451 } 452 } 453 } 454 455 private void addPredefinedProxies(List<String> knownClasses) { 456 knownClasses.add(CollectionProxy.ArrayListProxy.class.getName()); 457 knownClasses.add(CollectionProxy.LinkedListProxy.class.getName()); 458 knownClasses.add(CollectionProxy.HashSetProxy.class.getName()); 459 knownClasses.add(CollectionProxy.TreeSetProxy.class.getName()); 460 knownClasses.add(MapProxy.HashMapProxy.class.getName()); 461 knownClasses.add(MapProxy.TreeMapProxy.class.getName()); 462 } 463 464 /** 465 * Returns a map from format to a set of its superclass formats. The 466 * format for simple types, enums and class Object are not included. Only 467 * complex types have superclass formats as defined by 468 * Format.getSuperFormat. 469 */ 470 Map<Format,Set<Format>> getSubclassMap() { 471 Map<Format,Set<Format>> subclassMap = 472 new HashMap<Format,Set<Format>>(); 473 for (Format format : formatList) { 474 if (format == null || Format.isPredefined(format)) { 475 continue; 476 } 477 Format superFormat = format.getSuperFormat(); 478 if (superFormat != null) { 479 Set<Format> subclass = subclassMap.get(superFormat); 480 if (subclass == null) { 481 subclass = new HashSet<Format>(); 482 subclassMap.put(superFormat, subclass); 483 } 484 subclass.add(format); 485 } 486 } 487 return subclassMap; 488 } 489 490 /** 491 * Returns the model parameter, default model or stored model. 492 */ 493 public EntityModel getResolvedModel() { 494 return model; 495 } 496 497 /** 498 * Increments the reference count for a catalog that is already open. 499 */ 500 public void openExisting() { 501 openCount += 1; 502 } 503 504 /** 505 * Decrements the reference count and closes the catalog DB when it reaches 506 * zero. Returns true if the database was closed or false if the reference 507 * count is still non-zero and the database was left open. 508 */ 509 public boolean close() 510 throws DatabaseException { 511 512 if (openCount == 0) { 513 throw new IllegalStateException("Catalog is not open"); 514 } else { 515 openCount -= 1; 516 if (openCount == 0) { 517 Database dbToClose = db; 518 db = null; 519 dbToClose.close(); 520 return true; 521 } else { 522 return false; 523 } 524 } 525 } 526 527 /** 528 * Returns the current merged mutations. 529 */ 530 public Mutations getMutations() { 531 return mutations; 532 } 533 534 /** 535 * Convenience method that gets the class for the given class name and 536 * calls createFormat with the class object. 537 */ 538 public Format createFormat(String clsName, Map<String,Format> newFormats) { 539 Class type; 540 try { 541 type = SimpleCatalog.classForName(clsName); 542 } catch (ClassNotFoundException e) { 543 throw new IllegalStateException 544 ("Class does not exist: " + clsName); 545 } 546 return createFormat(type, newFormats); 547 } 548 549 /** 550 * If the given class format is not already present in the given map, 551 * creates an uninitialized format, adds it to the map, and also collects 552 * related formats in the map. 553 */ 554 public Format createFormat(Class type, Map<String,Format> newFormats) { 555 /* Return a new or existing format for this class. */ 556 String className = type.getName(); 557 Format format = newFormats.get(className); 558 if (format != null) { 559 return format; 560 } 561 format = formatMap.get(className); 562 if (format != null) { 563 return format; 564 } 565 /* Simple types are predefined. */ 566 assert !SimpleCatalog.isSimpleType(type) : className; 567 /* Create format of the appropriate type. */ 568 String proxyClassName = null; 569 if (proxyClassMap != null) { 570 proxyClassName = proxyClassMap.get(className); 571 } 572 if (proxyClassName != null) { 573 format = new ProxiedFormat(type, proxyClassName); 574 } else if (type.isArray()) { 575 format = type.getComponentType().isPrimitive() ? 576 (new PrimitiveArrayFormat(type)) : 577 (new ObjectArrayFormat(type)); 578 } else if (type.isEnum()) { 579 format = new EnumFormat(type); 580 } else if (type == Object.class || type.isInterface()) { 581 format = new NonPersistentFormat(type); 582 } else { 583 ClassMetadata metadata = model.getClassMetadata(className); 584 if (metadata == null) { 585 throw new IllegalArgumentException 586 ("Class could not be loaded or is not persistent: " + 587 className); 588 } 589 if (metadata.getCompositeKeyFields() != null && 590 (metadata.getPrimaryKey() != null || 591 metadata.getSecondaryKeys() != null)) { 592 throw new IllegalArgumentException 593 ("A composite key class may not have primary or" + 594 " secondary key fields: " + type.getName()); 595 } 596 try { 597 type.getDeclaredConstructor(); 598 } catch (NoSuchMethodException e) { 599 throw new IllegalArgumentException 600 ("No default constructor: " + type.getName(), e); 601 } 602 if (metadata.getCompositeKeyFields() != null) { 603 format = new CompositeKeyFormat 604 (type, metadata, metadata.getCompositeKeyFields()); 605 } else { 606 EntityMetadata entityMetadata = 607 model.getEntityMetadata(className); 608 format = new ComplexFormat(type, metadata, entityMetadata); 609 } 610 } 611 /* Collect new format along with any related new formats. */ 612 newFormats.put(className, format); 613 format.collectRelatedFormats(this, newFormats); 614 615 return format; 616 } 617 618 /** 619 * Adds a format and makes it the current format for the class. 620 */ 621 private void addFormat(Format format) { 622 addFormat(format, formatList, formatMap); 623 } 624 625 /** 626 * Adds a format to the given the format collections, for use when 627 * dynamically adding formats. 628 */ 629 private void addFormat(Format format, 630 List<Format> list, 631 Map<String,Format> map) { 632 format.setId(list.size()); 633 list.add(format); 634 map.put(format.getClassName(), format); 635 } 636 637 /** 638 * Installs an existing format when no evolution is needed, i.e, when the 639 * new and old formats are identical. 640 */ 641 void useExistingFormat(Format oldFormat) { 642 assert oldFormat.isCurrentVersion(); 643 formatMap.put(oldFormat.getClassName(), oldFormat); 644 } 645 646 /** 647 * Returns a set of all persistent (non-simple type) class names. 648 */ 649 Set<String> getModelClasses() { 650 Set<String> classes = new HashSet<String>(); 651 for (Format format : formatMap.values()) { 652 if (format.isModelClass()) { 653 classes.add(format.getClassName()); 654 } 655 } 656 return classes; 657 } 658 659 /** 660 * When a format is intialized, this method is called to get the version 661 * of the serialized object to be initialized. See Catalog. 662 */ 663 public int getInitVersion(Format format, boolean forReader) { 664 665 if (catalogData == null || catalogData.formatList == null || 666 format.getId() >= catalogData.formatList.size()) { 667 668 /* 669 * For new formats, use the current version. If catalogData is 670 * null, the Catalog ctor is finished and the format must be new. 671 * If the ctor is in progress, the format is new if its ID is 672 * greater than the ID of all pre-existing formats. 673 */ 674 return Catalog.CURRENT_VERSION; 675 } else { 676 677 /* 678 * Get the version of a pre-existing format during execution of the 679 * Catalog ctor. The catalogData field is non-null, but evolver 680 * may be null if the catalog is opened in raw mode. 681 */ 682 assert catalogData != null; 683 684 if (forReader) { 685 686 /* 687 * Get the version of the evolution reader for a pre-existing 688 * format. Use the current version if the format changed 689 * during class evolution, otherwise use the stored version. 690 */ 691 return (evolver != null && evolver.isFormatChanged(format)) ? 692 Catalog.CURRENT_VERSION : catalogData.version; 693 } else { 694 /* Always used the stored version for a pre-existing format. */ 695 return catalogData.version; 696 } 697 } 698 } 699 700 public Format getFormat(int formatId) { 701 try { 702 Format format = formatList.get(formatId); 703 if (format == null) { 704 throw new DeletedClassException 705 ("Format does not exist: " + formatId); 706 } 707 return format; 708 } catch (NoSuchElementException e) { 709 throw new DeletedClassException 710 ("Format does not exist: " + formatId); 711 } 712 } 713 714 715 /** 716 * Get a format for a given class, creating it if it does not exist. 717 * 718 * <p>This method is called for top level entity instances by 719 * PersistEntityBinding. When a new entity subclass format is added we 720 * call Store.openSecondaryIndexes so that previously unknown secondary 721 * databases can be created, before storing the entity. We do this here 722 * while not holding a synchronization mutex, not in addNewFormat, to avoid 723 * deadlocks. openSecondaryIndexes synchronizes on the Store. [#15247]</p> 724 */ 725 public Format getFormat(Class cls) { 726 Format format = formatMap.get(cls.getName()); 727 if (format == null) { 728 if (model != null) { 729 format = addNewFormat(cls); 730 /* Detect and handle new entity subclass. [#15247] */ 731 if (store != null) { 732 Format entityFormat = format.getEntityFormat(); 733 if (entityFormat != null && entityFormat != format) { 734 try { 735 store.openSecondaryIndexes 736 (null, entityFormat.getEntityMetadata(), null); 737 } catch (DatabaseException e) { 738 throw new RuntimeExceptionWrapper(e); 739 } 740 } 741 } 742 } 743 if (format == null) { 744 throw new IllegalArgumentException 745 ("Class is not persistent: " + cls.getName()); 746 } 747 } 748 return format; 749 } 750 751 public Format getFormat(String className) { 752 return formatMap.get(className); 753 } 754 755 public Format getLatestVersion(String className) { 756 return latestFormatMap.get(className); 757 } 758 759 /** 760 * Adds a format for a new class. Returns the format added for the given 761 * class, or throws an exception if the given class is not persistent. 762 * 763 * <p>This method uses a copy-on-write technique to add new formats without 764 * impacting other threads.</p> 765 */ 766 private synchronized Format addNewFormat(Class cls) { 767 768 /* 769 * After synchronizing, check whether another thread has added the 770 * format needed. Note that this is not the double-check technique 771 * because the formatMap field is volatile and is not itself checked 772 * for null. (The double-check technique is known to be flawed in 773 * Java.) 774 */ 775 Format format = formatMap.get(cls.getName()); 776 if (format != null) { 777 return format; 778 } 779 780 /* Copy the read-only format collections. */ 781 List<Format> newFormatList = new ArrayList<Format>(formatList); 782 Map<String,Format> newFormatMap = 783 new HashMap<String,Format>(formatMap); 784 Map<String,Format> newLatestFormatMap = 785 new HashMap<String,Format>(latestFormatMap); 786 787 /* Add the new format and all related new formats. */ 788 Map<String,Format> newFormats = new HashMap<String,Format>(); 789 format = createFormat(cls, newFormats); 790 for (Format newFormat : newFormats.values()) { 791 addFormat(newFormat, newFormatList, newFormatMap); 792 } 793 794 /* 795 * Initialize new formats using a read-only catalog because we can't 796 * update this catalog until after we store it (below). 797 */ 798 Catalog newFormatCatalog = 799 new ReadOnlyCatalog(newFormatList, newFormatMap); 800 for (Format newFormat : newFormats.values()) { 801 newFormat.initializeIfNeeded(newFormatCatalog); 802 newLatestFormatMap.put(newFormat.getClassName(), newFormat); 803 } 804 805 /* 806 * Write the updated catalog using auto-commit, then assign the new 807 * collections. The database write must occur before the collections 808 * are used, since a format must be persistent before it can be 809 * referenced by a data record. 810 */ 811 try { 812 Data catalogData = new Data(); 813 catalogData.formatList = newFormatList; 814 catalogData.mutations = mutations; 815 writeData(null, catalogData); 816 } catch (DatabaseException e) { 817 throw new RuntimeExceptionWrapper(e); 818 } 819 formatList = newFormatList; 820 formatMap = newFormatMap; 821 latestFormatMap = newLatestFormatMap; 822 823 return format; 824 } 825 826 /** 827 * Used to write the catalog when a format has been changed, for example, 828 * when Store.evolve has updated a Format's EvolveNeeded property. Uses 829 * auto-commit. 830 */ 831 public synchronized void flush() 832 throws DatabaseException { 833 834 Data catalogData = new Data(); 835 catalogData.formatList = formatList; 836 catalogData.mutations = mutations; 837 writeData(null, catalogData); 838 } 839 840 /** 841 * Reads catalog Data, converting old versions as necessary. An empty 842 * Data object is returned if no catalog data currently exists. Null is 843 * never returned. 844 */ 845 private Data readData(Transaction txn) 846 throws DatabaseException { 847 848 Data catalogData; 849 DatabaseEntry key = new DatabaseEntry(DATA_KEY); 850 DatabaseEntry data = new DatabaseEntry(); 851 OperationStatus status = db.get(txn, key, data, null); 852 if (status == OperationStatus.SUCCESS) { 853 ByteArrayInputStream bais = new ByteArrayInputStream 854 (data.getData(), data.getOffset(), data.getSize()); 855 try { 856 ObjectInputStream ois = new ObjectInputStream(bais); 857 Object object = ois.readObject(); 858 assert ois.available() == 0; 859 if (object instanceof Data) { 860 catalogData = (Data) object; 861 } else { 862 if (!(object instanceof List)) { 863 throw new IllegalStateException 864 (object.getClass().getName()); 865 } 866 catalogData = new Data(); 867 catalogData.formatList = (List) object; 868 catalogData.version = BETA_VERSION; 869 } 870 return catalogData; 871 } catch (ClassNotFoundException e) { 872 throw new DatabaseException(e); 873 } catch (IOException e) { 874 throw new DatabaseException(e); 875 } 876 } else { 877 catalogData = new Data(); 878 catalogData.version = Catalog.CURRENT_VERSION; 879 } 880 return catalogData; 881 } 882 883 /** 884 * Writes catalog Data. If txn is null, auto-commit is used. 885 */ 886 private void writeData(Transaction txn, Data catalogData) 887 throws DatabaseException { 888 889 /* Catalog data is written in the current version. */ 890 boolean wasBetaVersion = (catalogData.version == BETA_VERSION); 891 catalogData.version = CURRENT_VERSION; 892 893 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 894 try { 895 ObjectOutputStream oos = new ObjectOutputStream(baos); 896 oos.writeObject(catalogData); 897 } catch (IOException e) { 898 throw new DatabaseException(e); 899 } 900 DatabaseEntry key = new DatabaseEntry(DATA_KEY); 901 DatabaseEntry data = new DatabaseEntry(baos.toByteArray()); 902 db.put(txn, key, data); 903 904 /* 905 * Delete the unused beta mutations record if we read the beta version 906 * record earlier. 907 */ 908 if (wasBetaVersion) { 909 key.setData(BETA_MUTATIONS_KEY); 910 db.delete(txn, key); 911 } 912 } 913 914 public boolean isRawAccess() { 915 return rawAccess; 916 } 917 918 public Object convertRawObject(RawObject o, IdentityHashMap converted) { 919 Format format = (Format) o.getType(); 920 if (this != format.getCatalog()) { 921 String className = format.getClassName(); 922 format = getFormat(className); 923 if (format == null) { 924 throw new IllegalArgumentException 925 ("External raw type not found: " + className); 926 } 927 } 928 Format proxiedFormat = format.getProxiedFormat(); 929 if (proxiedFormat != null) { 930 format = proxiedFormat; 931 } 932 if (converted == null) { 933 converted = new IdentityHashMap(); 934 } 935 return format.convertRawObject(this, false, o, converted); 936 } 937 938 public void dump() { 939 System.out.println("--- Begin formats ---"); 940 for (Format format : formatList) { 941 if (format != null) { 942 System.out.println 943 ("ID: " + format.getId() + 944 " class: " + format.getClassName() + 945 " version: " + format.getVersion() + 946 " current: " + 947 (format == formatMap.get(format.getClassName()))); 948 } 949 } 950 System.out.println("--- End formats ---"); 951 } 952} 953