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