
org.zoodb.jdo.internal.ZooClassDef Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of parent Show documentation
Show all versions of parent Show documentation
ZooDB Java JDO Object Database.
The newest version!
/*
* Copyright 2009-2013 Tilmann Zaeschke. All rights reserved.
*
* This file is part of ZooDB.
*
* ZooDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZooDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZooDB. If not, see .
*
* See the README and COPYING files for further information.
*/
package org.zoodb.jdo.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.zoodb.api.impl.ZooPCImpl;
import org.zoodb.jdo.internal.ZooFieldDef.JdoType;
import org.zoodb.jdo.internal.client.PCContext;
import org.zoodb.jdo.internal.client.SchemaOperation;
import org.zoodb.jdo.internal.client.session.ClientSessionCache;
import org.zoodb.jdo.internal.util.DBLogger;
import org.zoodb.jdo.internal.util.Util;
/**
* ZooClassDef represents a class schema definition used by the database.
* The highest stored schema is that of PersistenceCapableImpl.
*
* Initialization takes three(four) steps:
* 1) Instance creation and association with super OID.
* 2) Association with super class (this may not be instantiated during 1).
* 3) Initialization of fields and their offsets. This requires a complete inheritance hierarchy.
* 4) Update FCO fields with ClassDef OIDs.
*
* @author Tilmann Zaeschke
*/
public class ZooClassDef extends ZooPCImpl {
private String className;
private transient Class> cls;
private long oidSuper;
//This is a unique Schema ID. All versions of a schema have the same ID.
private final long schemaId;
private final short versionId;
private transient ZooClassDef superDef;
private transient ZooClassProxy versionProxy = null;
private final ArrayList localFields = new ArrayList(10);
private transient ZooFieldDef[] allFields = new ZooFieldDef[0];
private transient HashMap fieldBuffer = null;
private transient PCContext providedContext = null;
private long prevVersionOid = 0;
private transient ZooClassDef nextVersion = null;
private transient ZooClassDef prevVersion = null;
//Indicates whether the class is schema-compatible with the Java class of the same name
private transient boolean isJavaCompatible = false;
//List of operations that transform a previous version into the current version.
private ArrayList evolutionOperations = null;
private ZooClassDef() {
//DO not use, for de-serializer only!
oidSuper = 0;
schemaId = 0;
versionId = -1;
}
private ZooClassDef(String clsName, long oid, long superOid, long schemaId, int versionId) {
jdoZooSetOid(oid);
this.className = clsName;
this.oidSuper = superOid;
this.schemaId = schemaId;
this.versionId = (short) versionId;
}
/**
* Methods used for bootstrapping the schema of newly created databases.
* @return Root schema
*/
public static ZooClassDef bootstrapZooPCImpl() {
//The bootstrapped schemata have a fixed OID.
//This is because they also need to be created every time we open a database.
//They are required to actually read the boot-schema from the database ....
//Actually, we don't really need to read them, but they shold be in the database
//anyway, for consistency.
//TODO maybe we don't need to store ZooClassDef????
// -> and ZooPC does not need to be bootstrapped in memory????
ZooClassDef x = new ZooClassDef(ZooPCImpl.class.getName(), 50, 0, 50, 0);
x.cls = ZooPCImpl.class;
x.className = ZooPCImpl.class.getName();
return x;
}
/**
* Methods used for bootstrapping the schema of newly created databases.
* @return Meta schema instance
*/
public static ZooClassDef bootstrapZooClassDef() {
ZooClassDef meta = new ZooClassDef(ZooClassDef.class.getName(), 51, 50, 51, 0);
ArrayList fields = new ArrayList();
fields.add(new ZooFieldDef(meta, "className", String.class.getName(), 0,
JdoType.STRING, 70));
fields.add(new ZooFieldDef(meta, "oidSuper", long.class.getName(), 0,
JdoType.PRIMITIVE, 71));
fields.add(new ZooFieldDef(meta, "schemaId", long.class.getName(), 0,
JdoType.PRIMITIVE, 72));
fields.add(new ZooFieldDef(meta, "versionId", short.class.getName(), 0,
JdoType.PRIMITIVE, 73));
fields.add(new ZooFieldDef(meta, "localFields", ArrayList.class.getName(), 0,
JdoType.SCO, 74));
fields.add(new ZooFieldDef(meta, "prevVersionOid", long.class.getName(), 0,
JdoType.PRIMITIVE, 75));
fields.add(new ZooFieldDef(meta, "evolutionOperations", ArrayList.class.getName(), 0,
JdoType.SCO, 76));
//new ZooFieldDef(this, allFields, ZooFieldDef[].class.getName(), typeOid, JdoType.ARRAY);
meta.registerFields(fields);
meta.cls = ZooClassDef.class;
meta.className = ZooClassDef.class.getName();
return meta;
}
public ZooClassDef getModifiableVersion(ClientSessionCache cache, List ops) {
return getModifiableVersion(cache, ops, null);
}
public ZooClassDef getModifiableVersion(ClientSessionCache cache, List ops,
ZooClassDef newSuper) {
if (this.jdoZooIsNew()) {
//this happens for example when the super-class is modified AFTER the local class got
//a new version.
//In other words:
//First we add all sub-classes. This is important, because getModifiableVersion() only
//add a new version to the super-class if it actually creates a new version. If there
//is already a modifiable sub-class, it would not be added.
ensureLatestSuper();
return this;
}
ZooClassDef defNew = newVersion(cache, ops, newSuper);
ops.add(new SchemaOperation.SchemaNewVersion(this, defNew, cache));
for (ZooClassProxy sub: versionProxy.getSubProxies()) {
//ensure that all sub-classes become modifiable versions.
sub.getSchemaDef().getModifiableVersion(cache, ops, defNew);
}
return defNew;
}
/**
* Schema versioning: We only create new schema instance when we add or remove fields.
* Renaming a field should not result in a new version!
* A new version is only required when the modified schema does not match the stored data. Such
* changes require also new versions of all sub-classes.
* WHY? If every class stored only their own fields would we still have a problem? Yes,
* because the new version of the referenced superclass has a different OID.
* @param cache
*
* @return New version.
*/
private ZooClassDef newVersion(ClientSessionCache cache, List ops,
ZooClassDef newSuper) {
if (nextVersion != null) {
throw new IllegalStateException();
}
if (newSuper == null) {
//no new version of super available
newSuper = superDef;
}
long oid = jdoZooGetContext().getNode().getOidBuffer().allocateOid();
ZooClassDef newDef = new ZooClassDef(className, oid, newSuper.getOid(), schemaId,
versionId + 1);
//super-class
newDef.associateSuperDef(newSuper);
//caches
cache.addSchema(newDef, false, jdoZooGetContext().getNode());
//versions
newDef.prevVersionOid = jdoZooGetOid();
newDef.prevVersion = this;
nextVersion = newDef;
//API class
newDef.versionProxy = versionProxy;
versionProxy.newVersion(newDef);
//context
newDef.providedContext =
new PCContext(newDef, providedContext.getSession(), providedContext.getNode());
//fields
for (ZooFieldDef f: localFields) {
ZooFieldDef fNew = new ZooFieldDef(f, newDef);
newDef.localFields.add(fNew);
if (fNew.getProxy() != null) {
fNew.getProxy().updateVersion(fNew);
}
}
newDef.associateFields();
return newDef;
}
public void ensureLatestSuper() {
//This is also the general population function for the sub-class list
if (superDef.getNextVersion() != null) {
superDef = superDef.getNextVersion();
//This is the only place where we may change the super-oid, because the local class is
//modifiable and the super-class becomes modifiable as well.
oidSuper = superDef.getOid();
}
if (superDef.getNextVersion() != null) {
throw new IllegalStateException();
}
}
public ZooClassDef newVersionRollback(ZooClassDef newDef, ClientSessionCache cache) {
if (nextVersion != newDef) {
throw new IllegalStateException();
}
if (newDef.nextVersion != null) {
throw new IllegalStateException();
}
//caches
newDef.jdoZooMarkDeleted();
cache.updateSchema(this, newDef.getJavaClass(), this.getJavaClass());
//versions
nextVersion = null;
//API class
versionProxy = newDef.versionProxy;
versionProxy.newVersionRollback(newDef);
//fields
for (ZooFieldDef f: localFields) {
if (f.getProxy() != null) {
f.getProxy().updateVersion(f);
}
}
//sub-classes are initialised via ensureLatestSuper().
return newDef;
}
public static ZooClassDef declare(String clsName, long oid, long superOid) {
return new ZooClassDef(clsName, oid, superOid, oid, 0);
}
public static ZooClassDef createFromJavaType(Class> cls, ZooClassDef defSuper,
Node node, Session session) {
//create instance
ZooClassDef def;
long superOid = 0;
if (cls != ZooPCImpl.class) {
if (defSuper == null) {
throw DBLogger.newUser("Super class is not persistent capable: " + cls);
}
superOid = defSuper.getOid();
if (superOid == 0) {
throw DBLogger.newFatal("No super class found: " + cls.getName());
}
}
long oid = node.getOidBuffer().allocateOid();
def = new ZooClassDef(cls.getName(), oid, superOid, oid, 0);
//local fields:
ArrayList fieldList = new ArrayList();
Field[] fields = cls.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field jField = fields[i];
if (Modifier.isStatic(jField.getModifiers()) ||
Modifier.isTransient(jField.getModifiers())) {
continue;
}
//we cannot set references to other ZooClassDefs yet, as they may not be made
//persistent yet
long fieldOid = node.getOidBuffer().allocateOid();
ZooFieldDef zField = ZooFieldDef.createFromJavaType(def, jField, fieldOid);
fieldList.add(zField);
}
// init class
def.registerFields(fieldList);
def.cls = cls;
def.associateSuperDef(defSuper);
def.associateProxy(new ZooClassProxy(def, session));
def.associateFields();
return def;
}
public void initProvidedContext(Session session, Node node) {
if (providedContext != null) {
if (!className.equals(ZooClassDef.class.getName())) {
throw new IllegalStateException(className);
} else {
//ignore resetting attempt for ZooClassDef
return;
}
}
providedContext = new PCContext(this, session, node);
}
/**
*
* @return The context that this classDef provides for instances of its class.
*/
public final PCContext getProvidedContext() {
return providedContext;
}
void registerFields(List fieldList) {
localFields.addAll(fieldList);
}
public void associateFCOs(Collection cachedSchemata) {
//Fields:
for (ZooFieldDef zField: localFields) {
String typeName = zField.getTypeName();
if (zField.isPrimitiveType()) {
//no further work for primitives
continue;
}
ZooClassDef typeDef = null;
for (ZooClassDef cs: cachedSchemata) {
if (cs.getClassName().equals(typeName)) {
typeDef = cs;
break;
}
}
if (typeDef==null) {
//found SCO
continue;
}
zField.setType(typeDef);
}
}
public String getClassName() {
return className;
}
public long getOid() {
return jdoZooGetOid();
}
public Class> getJavaClass() {
return cls;
}
public void associateJavaTypes() {
associateJavaTypes(false);
}
public void associateJavaTypes(boolean failForMismatch) {
if (cls != null) {
if (!className.equals(ZooClassDef.class.getName()) &&
!className.equals(ZooPCImpl.class.getName())) {
System.out.println("This is new, FIX this!"); //TODO remove
return;
//throw new IllegalStateException(cls.getName());
}
}
if (nextVersion != null) {
//Java classes are unlikely to fit with outdated schemas
return;
}
String fName = null;
try {
Class> tmpClass = Class.forName(className);
for (ZooFieldDef f: localFields) {
fName = f.getName();
Field jf = tmpClass.getDeclaredField(fName);
//this may fail due to incompatibilities!
f.setJavaField(jf);
}
isJavaCompatible = true;
cls = tmpClass;
} catch (ClassNotFoundException e) {
//okay we will use artifical/generic classes
return;
} catch (SecurityException e) {
throw DBLogger.newFatal("No access to class fields: " + className + "." + fName, e);
} catch (NoSuchFieldException e) {
//okay, Java class is incompatible. We continue anyway, but ensure that the
//Java deserializer is not used.
for (ZooFieldDef f: localFields) {
f.unsetJavaField();
}
return;
}
// We check field mismatches and missing Java fields above.
// Now check field count, this should cover missing schema fields (too many Java fields).
// we need to filter out transient and static fields
int n = 0;
for (Field f: cls.getDeclaredFields()) {
int mod = f.getModifiers();
if (Modifier.isTransient(mod) || Modifier.isStatic(mod)) {
continue;
}
n++;
}
if (localFields.size() != n) {
cls = null;
if (failForMismatch) {
throw DBLogger.newUser("Schema error, field count mismatch between Java class (" +
n + ") and database class (" + localFields.size() + ").");
}
}
}
public ArrayList getLocalFields() {
return localFields;
}
public ZooFieldDef[] getAllFields() {
return allFields;
}
public ZooClassProxy getVersionProxy() {
return versionProxy;
}
public long getSuperOID() {
return oidSuper;
}
public ZooClassDef getSuperDef() {
return superDef;
}
public void associateVersions(Map schemata) {
if (prevVersionOid != 0) {
prevVersion = schemata.get(prevVersionOid);
prevVersion.nextVersion = this;
}
}
/**
* Only to be used during database startup to load the schema-tree.
* @param superDef
*/
public void associateSuperDef(ZooClassDef superDef) {
if (this.superDef != null) {
throw new IllegalStateException();
}
if (superDef == null) {
throw new IllegalArgumentException();
}
//class invariant
if (superDef.getOid() != oidSuper) {
throw new IllegalStateException("s-oid= " + oidSuper + " / " + superDef.getOid() +
" class=" + className);
}
this.superDef = superDef;
}
public void associateFields() {
ArrayList allFields = new ArrayList();
//For PersistenceCapableImpl _super may be null:
ZooClassDef sup = superDef;
while (sup != null) {
allFields.addAll(0, sup.getLocalFields());
sup = sup.superDef;
}
int ofs = ZooFieldDef.OFS_INIITIAL; //8 + 8; //Schema-OID + OID
if (!allFields.isEmpty()) {
ofs = allFields.get(allFields.size()-1).getNextOffset();
}
//local fields:
int i = allFields.size();
for (ZooFieldDef f: localFields) {
f.setOffset(ofs, i);
ofs = f.getNextOffset();
allFields.add(f);
i++;
}
this.allFields = allFields.toArray(new ZooFieldDef[allFields.size()]);
}
public ZooFieldDef getField(String attrName) {
for (ZooFieldDef f: allFields) {
if (f.getName().equals(attrName)) {
return f;
}
}
throw DBLogger.newUser("Field name not found: " + attrName);
}
public Map getAllFieldsAsMap() {
if (fieldBuffer == null) {
fieldBuffer = new HashMap();
for (ZooFieldDef def: getAllFields()) {
fieldBuffer.put(def.getName(), def);
}
}
return fieldBuffer;
}
public boolean hasSuperClass(ZooClassDef cls) {
if (superDef == cls) {
return true;
}
if (superDef == null) {
return false;
}
return superDef.hasSuperClass(cls);
}
@Override
public String toString() {
return className + " (" + Util.oidToString(getOid()) + ") super=" + superDef;
}
public void rename(String newName) {
zooActivateWrite();
className = newName;
cls = null;
fieldBuffer = null;
associateJavaTypes();
}
public void addField(ZooFieldDef field) {
localFields.add(field);
rebuildFieldsRecursive();
newEvolutionOperationAdd(allFields.length-1, null);
}
void rebuildFieldsRecursive() {
associateFields();
for (ZooClassProxy c: versionProxy.getSubProxies()) {
c.getSchemaDef().rebuildFieldsRecursive();
}
}
public void removeField(ZooFieldDef fieldDef) {
int i = 0;
// remove from localFields
for (ZooFieldDef fd: localFields) {
if (fd.getName().equals(fieldDef.getName())) {
localFields.remove(i);
break;
}
i++;
}
// for op, use position in allFields
i = 0;
for (ZooFieldDef fd: allFields) {
if (fd.getName().equals(fieldDef.getName())) {
rebuildFieldsRecursive();
newEvolutionOperationRemove(i);
return;
}
i++;
}
throw new IllegalStateException("Field not found: " + fieldDef);
}
public ZooClassDef getNextVersion() {
return nextVersion;
}
public ZooClassDef getPreviousVersion() {
return prevVersion;
}
private void newEvolutionOperation(PersistentSchemaOperation op) {
if (evolutionOperations == null) {
evolutionOperations = new ArrayList();
}
evolutionOperations.add(op);
}
private void newEvolutionOperationAdd(int fieldId, Object initialValue) {
newEvolutionOperation(PersistentSchemaOperation.newAddOperation(
fieldId, allFields[fieldId], initialValue));
for (ZooClassProxy sub: versionProxy.getSubProxies()) {
sub.getSchemaDef().newEvolutionOperationAdd(fieldId, initialValue);
}
}
private void newEvolutionOperationRemove(int fieldId) {
newEvolutionOperation(PersistentSchemaOperation.newRemoveOperation(fieldId));
for (ZooClassProxy sub: versionProxy.getSubProxies()) {
sub.getSchemaDef().newEvolutionOperationRemove(fieldId);
}
}
/**
* The List of evolution operations contains all operations that are required to turn a
* previous schema version into the present schema version. This includes also operations
* on super-classes. Field-IDs are relative to the allFields[].
*
* @return List of operations
*/
public List getEvolutionOps() {
return evolutionOperations;
}
/**
* Returns the unique schema ID which is independent of the schema version.
*/
public long getSchemaId() {
return schemaId;
}
/**
* Returns the version number of this schema version, starting with 0.
*/
public int getSchemaVersion() {
return versionId;
}
public void associateProxy(ZooClassProxy px) {
if (versionProxy != null) {
throw new IllegalStateException();
}
versionProxy = px;
}
/**
*
* @param def
* @return True if this class is the same or a super-type of 'def'. Otherwise returns false.
*/
public boolean isSuperTypeOf(ZooClassDef def) {
if (this == def) {
return true;
}
if (def.superDef == null) {
return false;
}
return isSuperTypeOf(def.superDef);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy