PersistCatalog.java [plain text]
package com.sleepycat.persist.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.db.Database;
import com.sleepycat.db.DatabaseConfig;
import com.sleepycat.db.DatabaseEntry;
import com.sleepycat.db.DatabaseException;
import com.sleepycat.db.Environment;
import com.sleepycat.db.OperationStatus;
import com.sleepycat.db.Transaction;
import com.sleepycat.persist.DatabaseNamer;
import com.sleepycat.persist.evolve.DeletedClassException;
import com.sleepycat.persist.evolve.IncompatibleClassException;
import com.sleepycat.persist.evolve.Mutations;
import com.sleepycat.persist.evolve.Renamer;
import com.sleepycat.persist.model.AnnotationModel;
import com.sleepycat.persist.model.ClassMetadata;
import com.sleepycat.persist.model.EntityMetadata;
import com.sleepycat.persist.model.EntityModel;
import com.sleepycat.persist.raw.RawObject;
import com.sleepycat.util.RuntimeExceptionWrapper;
public class PersistCatalog implements Catalog {
private static final byte[] DATA_KEY = getIntBytes(-1);
private static final byte[] BETA_MUTATIONS_KEY = getIntBytes(-2);
private static byte[] getIntBytes(int val) {
DatabaseEntry entry = new DatabaseEntry();
IntegerBinding.intToEntry(val, entry);
assert entry.getSize() == 4 && entry.getData().length == 4;
return entry.getData();
}
public static boolean expectNoClassChanges;
public static boolean unevolvedFormatsEncountered;
private static class Data implements Serializable {
static final long serialVersionUID = 7515058069137413261L;
List<Format> formatList;
Mutations mutations;
int version;
}
private volatile List<Format> formatList;
private volatile Map<String,Format> formatMap;
private volatile Map<String,Format> latestFormatMap;
private Map<String,String> proxyClassMap;
private boolean rawAccess;
private EntityModel model;
private Mutations mutations;
private Database db;
private int openCount;
private Store store;
private Evolver evolver;
private Data catalogData;
public PersistCatalog(Transaction txn,
Environment env,
String storePrefix,
String dbName,
DatabaseConfig dbConfig,
EntityModel modelParam,
Mutations mutationsParam,
boolean rawAccess,
Store store)
throws DatabaseException {
this.rawAccess = rawAccess;
this.store = store;
String[] fileAndDbNames = (store != null) ?
store.parseDbName(dbName) :
Store.parseDbName(dbName, DatabaseNamer.DEFAULT);
try {
db = DbCompat.openDatabase
(env, txn, fileAndDbNames[0], fileAndDbNames[1],
dbConfig);
} catch (FileNotFoundException e) {
throw new DatabaseException(e);
}
openCount = 1;
boolean success = false;
try {
catalogData = readData(txn);
mutations = catalogData.mutations;
if (mutations == null) {
mutations = new Mutations();
}
boolean betaVersion = (catalogData.version == BETA_VERSION);
boolean forceWriteData = betaVersion;
boolean disallowClassChanges = betaVersion;
boolean forceEvolution = false;
if (mutationsParam != null &&
!mutations.equals(mutationsParam)) {
mutations = mutationsParam;
forceWriteData = true;
forceEvolution = true;
}
formatList = catalogData.formatList;
if (formatList == null) {
formatList = SimpleCatalog.copyFormatList();
Format format = new NonPersistentFormat(Object.class);
format.setId(Format.ID_OBJECT);
formatList.set(Format.ID_OBJECT, format);
format = new NonPersistentFormat(Number.class);
format.setId(Format.ID_NUMBER);
formatList.set(Format.ID_NUMBER, format);
} else {
if (SimpleCatalog.copyMissingFormats(formatList)) {
forceWriteData = true;
}
}
if (betaVersion) {
Map<String,Format> formatMap = new HashMap<String,Format>();
for (Format format : formatList) {
if (format != null) {
formatMap.put(format.getClassName(), format);
}
}
for (Format format : formatList) {
if (format != null) {
format.migrateFromBeta(formatMap);
}
}
}
formatMap = new HashMap<String,Format>(formatList.size());
latestFormatMap = new HashMap<String,Format>(formatList.size());
if (rawAccess) {
for (Format format : formatList) {
if (format != null) {
String name = format.getClassName();
if (format.isCurrentVersion()) {
formatMap.put(name, format);
}
if (format == format.getLatestVersion()) {
latestFormatMap.put(name, format);
}
}
}
for (Format format : formatList) {
if (format != null) {
format.initializeIfNeeded(this);
}
}
model = new StoredModel(this);
success = true;
return;
}
if (modelParam != null) {
model = modelParam;
} else {
model = new AnnotationModel();
}
for (int i = 0; i <= Format.ID_PREDEFINED; i += 1) {
Format simpleFormat = formatList.get(i);
if (simpleFormat != null) {
formatMap.put(simpleFormat.getClassName(), simpleFormat);
}
}
List<String> knownClasses =
new ArrayList<String>(model.getKnownClasses());
addPredefinedProxies(knownClasses);
proxyClassMap = new HashMap<String,String>();
for (Format oldFormat : formatList) {
if (oldFormat == null || Format.isPredefined(oldFormat)) {
continue;
}
String oldName = oldFormat.getClassName();
Renamer renamer = mutations.getRenamer
(oldName, oldFormat.getVersion(), null);
String newName =
(renamer != null) ? renamer.getNewName() : oldName;
addProxiedClass(newName);
}
for (String className : knownClasses) {
addProxiedClass(className);
}
Map<String,Format> newFormats = new HashMap<String,Format>();
for (String className : knownClasses) {
createFormat(className, newFormats);
}
evolver = new Evolver
(this, storePrefix, mutations, newFormats, forceEvolution,
disallowClassChanges);
for (Format oldFormat : formatList) {
if (oldFormat == null || Format.isPredefined(oldFormat)) {
continue;
}
if (oldFormat.isEntity()) {
evolver.evolveFormat(oldFormat);
} else {
evolver.addNonEntityFormat(oldFormat);
}
}
evolver.finishEvolution();
String errors = evolver.getErrors();
if (errors != null) {
throw new IncompatibleClassException(errors);
}
for (Format newFormat : newFormats.values()) {
addFormat(newFormat);
}
for (Format format : formatList) {
if (format != null) {
format.initializeIfNeeded(this);
if (format == format.getLatestVersion()) {
latestFormatMap.put(format.getClassName(), format);
}
}
}
boolean needWrite =
newFormats.size() > 0 ||
evolver.areFormatsChanged();
if (expectNoClassChanges && needWrite) {
throw new IllegalStateException
("Unexpected changes " +
" newFormats.size=" + newFormats.size() +
" areFormatsChanged=" + evolver.areFormatsChanged());
}
if ((needWrite || forceWriteData) &&
!db.getConfig().getReadOnly()) {
evolver.renameAndRemoveDatabases(store, txn);
catalogData.formatList = formatList;
catalogData.mutations = mutations;
writeData(txn, catalogData);
} else if (forceWriteData) {
throw new IllegalArgumentException
("When an upgrade is required the store may not be " +
"opened read-only");
}
success = true;
} finally {
proxyClassMap = null;
catalogData = null;
evolver = null;
if (!success) {
close();
}
}
}
public void getEntityFormats(Collection<Format> entityFormats) {
for (Format format : formatMap.values()) {
if (format.isEntity()) {
entityFormats.add(format);
}
}
}
private void addProxiedClass(String className) {
ClassMetadata metadata = model.getClassMetadata(className);
if (metadata != null) {
String proxiedClassName = metadata.getProxiedClassName();
if (proxiedClassName != null) {
proxyClassMap.put(proxiedClassName, className);
}
}
}
private void addPredefinedProxies(List<String> knownClasses) {
knownClasses.add(CollectionProxy.ArrayListProxy.class.getName());
knownClasses.add(CollectionProxy.LinkedListProxy.class.getName());
knownClasses.add(CollectionProxy.HashSetProxy.class.getName());
knownClasses.add(CollectionProxy.TreeSetProxy.class.getName());
knownClasses.add(MapProxy.HashMapProxy.class.getName());
knownClasses.add(MapProxy.TreeMapProxy.class.getName());
}
Map<Format,Set<Format>> getSubclassMap() {
Map<Format,Set<Format>> subclassMap =
new HashMap<Format,Set<Format>>();
for (Format format : formatList) {
if (format == null || Format.isPredefined(format)) {
continue;
}
Format superFormat = format.getSuperFormat();
if (superFormat != null) {
Set<Format> subclass = subclassMap.get(superFormat);
if (subclass == null) {
subclass = new HashSet<Format>();
subclassMap.put(superFormat, subclass);
}
subclass.add(format);
}
}
return subclassMap;
}
public EntityModel getResolvedModel() {
return model;
}
public void openExisting() {
openCount += 1;
}
public boolean close()
throws DatabaseException {
if (openCount == 0) {
throw new IllegalStateException("Catalog is not open");
} else {
openCount -= 1;
if (openCount == 0) {
Database dbToClose = db;
db = null;
dbToClose.close();
return true;
} else {
return false;
}
}
}
public Mutations getMutations() {
return mutations;
}
public Format createFormat(String clsName, Map<String,Format> newFormats) {
Class type;
try {
type = SimpleCatalog.classForName(clsName);
} catch (ClassNotFoundException e) {
throw new IllegalStateException
("Class does not exist: " + clsName);
}
return createFormat(type, newFormats);
}
public Format createFormat(Class type, Map<String,Format> newFormats) {
String className = type.getName();
Format format = newFormats.get(className);
if (format != null) {
return format;
}
format = formatMap.get(className);
if (format != null) {
return format;
}
assert !SimpleCatalog.isSimpleType(type) : className;
String proxyClassName = null;
if (proxyClassMap != null) {
proxyClassName = proxyClassMap.get(className);
}
if (proxyClassName != null) {
format = new ProxiedFormat(type, proxyClassName);
} else if (type.isArray()) {
format = type.getComponentType().isPrimitive() ?
(new PrimitiveArrayFormat(type)) :
(new ObjectArrayFormat(type));
} else if (type.isEnum()) {
format = new EnumFormat(type);
} else if (type == Object.class || type.isInterface()) {
format = new NonPersistentFormat(type);
} else {
ClassMetadata metadata = model.getClassMetadata(className);
if (metadata == null) {
throw new IllegalArgumentException
("Class could not be loaded or is not persistent: " +
className);
}
if (metadata.getCompositeKeyFields() != null &&
(metadata.getPrimaryKey() != null ||
metadata.getSecondaryKeys() != null)) {
throw new IllegalArgumentException
("A composite key class may not have primary or" +
" secondary key fields: " + type.getName());
}
try {
type.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException
("No default constructor: " + type.getName(), e);
}
if (metadata.getCompositeKeyFields() != null) {
format = new CompositeKeyFormat
(type, metadata, metadata.getCompositeKeyFields());
} else {
EntityMetadata entityMetadata =
model.getEntityMetadata(className);
format = new ComplexFormat(type, metadata, entityMetadata);
}
}
newFormats.put(className, format);
format.collectRelatedFormats(this, newFormats);
return format;
}
private void addFormat(Format format) {
addFormat(format, formatList, formatMap);
}
private void addFormat(Format format,
List<Format> list,
Map<String,Format> map) {
format.setId(list.size());
list.add(format);
map.put(format.getClassName(), format);
}
void useExistingFormat(Format oldFormat) {
assert oldFormat.isCurrentVersion();
formatMap.put(oldFormat.getClassName(), oldFormat);
}
Set<String> getModelClasses() {
Set<String> classes = new HashSet<String>();
for (Format format : formatMap.values()) {
if (format.isModelClass()) {
classes.add(format.getClassName());
}
}
return classes;
}
public int getInitVersion(Format format, boolean forReader) {
if (catalogData == null || catalogData.formatList == null ||
format.getId() >= catalogData.formatList.size()) {
return Catalog.CURRENT_VERSION;
} else {
assert catalogData != null;
if (forReader) {
return (evolver != null && evolver.isFormatChanged(format)) ?
Catalog.CURRENT_VERSION : catalogData.version;
} else {
return catalogData.version;
}
}
}
public Format getFormat(int formatId) {
try {
Format format = formatList.get(formatId);
if (format == null) {
throw new DeletedClassException
("Format does not exist: " + formatId);
}
return format;
} catch (NoSuchElementException e) {
throw new DeletedClassException
("Format does not exist: " + formatId);
}
}
public Format getFormat(Class cls) {
Format format = formatMap.get(cls.getName());
if (format == null) {
if (model != null) {
format = addNewFormat(cls);
if (store != null) {
Format entityFormat = format.getEntityFormat();
if (entityFormat != null && entityFormat != format) {
try {
store.openSecondaryIndexes
(null, entityFormat.getEntityMetadata(), null);
} catch (DatabaseException e) {
throw new RuntimeExceptionWrapper(e);
}
}
}
}
if (format == null) {
throw new IllegalArgumentException
("Class is not persistent: " + cls.getName());
}
}
return format;
}
public Format getFormat(String className) {
return formatMap.get(className);
}
public Format getLatestVersion(String className) {
return latestFormatMap.get(className);
}
private synchronized Format addNewFormat(Class cls) {
Format format = formatMap.get(cls.getName());
if (format != null) {
return format;
}
List<Format> newFormatList = new ArrayList<Format>(formatList);
Map<String,Format> newFormatMap =
new HashMap<String,Format>(formatMap);
Map<String,Format> newLatestFormatMap =
new HashMap<String,Format>(latestFormatMap);
Map<String,Format> newFormats = new HashMap<String,Format>();
format = createFormat(cls, newFormats);
for (Format newFormat : newFormats.values()) {
addFormat(newFormat, newFormatList, newFormatMap);
}
Catalog newFormatCatalog =
new ReadOnlyCatalog(newFormatList, newFormatMap);
for (Format newFormat : newFormats.values()) {
newFormat.initializeIfNeeded(newFormatCatalog);
newLatestFormatMap.put(newFormat.getClassName(), newFormat);
}
try {
Data catalogData = new Data();
catalogData.formatList = newFormatList;
catalogData.mutations = mutations;
writeData(null, catalogData);
} catch (DatabaseException e) {
throw new RuntimeExceptionWrapper(e);
}
formatList = newFormatList;
formatMap = newFormatMap;
latestFormatMap = newLatestFormatMap;
return format;
}
public synchronized void flush()
throws DatabaseException {
Data catalogData = new Data();
catalogData.formatList = formatList;
catalogData.mutations = mutations;
writeData(null, catalogData);
}
private Data readData(Transaction txn)
throws DatabaseException {
Data catalogData;
DatabaseEntry key = new DatabaseEntry(DATA_KEY);
DatabaseEntry data = new DatabaseEntry();
OperationStatus status = db.get(txn, key, data, null);
if (status == OperationStatus.SUCCESS) {
ByteArrayInputStream bais = new ByteArrayInputStream
(data.getData(), data.getOffset(), data.getSize());
try {
ObjectInputStream ois = new ObjectInputStream(bais);
Object object = ois.readObject();
assert ois.available() == 0;
if (object instanceof Data) {
catalogData = (Data) object;
} else {
if (!(object instanceof List)) {
throw new IllegalStateException
(object.getClass().getName());
}
catalogData = new Data();
catalogData.formatList = (List) object;
catalogData.version = BETA_VERSION;
}
return catalogData;
} catch (ClassNotFoundException e) {
throw new DatabaseException(e);
} catch (IOException e) {
throw new DatabaseException(e);
}
} else {
catalogData = new Data();
catalogData.version = Catalog.CURRENT_VERSION;
}
return catalogData;
}
private void writeData(Transaction txn, Data catalogData)
throws DatabaseException {
boolean wasBetaVersion = (catalogData.version == BETA_VERSION);
catalogData.version = CURRENT_VERSION;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(catalogData);
} catch (IOException e) {
throw new DatabaseException(e);
}
DatabaseEntry key = new DatabaseEntry(DATA_KEY);
DatabaseEntry data = new DatabaseEntry(baos.toByteArray());
db.put(txn, key, data);
if (wasBetaVersion) {
key.setData(BETA_MUTATIONS_KEY);
db.delete(txn, key);
}
}
public boolean isRawAccess() {
return rawAccess;
}
public Object convertRawObject(RawObject o, IdentityHashMap converted) {
Format format = (Format) o.getType();
if (this != format.getCatalog()) {
String className = format.getClassName();
format = getFormat(className);
if (format == null) {
throw new IllegalArgumentException
("External raw type not found: " + className);
}
}
Format proxiedFormat = format.getProxiedFormat();
if (proxiedFormat != null) {
format = proxiedFormat;
}
if (converted == null) {
converted = new IdentityHashMap();
}
return format.convertRawObject(this, false, o, converted);
}
public void dump() {
System.out.println("--- Begin formats ---");
for (Format format : formatList) {
if (format != null) {
System.out.println
("ID: " + format.getId() +
" class: " + format.getClassName() +
" version: " + format.getVersion() +
" current: " +
(format == formatMap.get(format.getClassName())));
}
}
System.out.println("--- End formats ---");
}
}