/*
* Copyright 2009-2016 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 <http://www.gnu.org/licenses/>.
*
* See the README and COPYING files for further information.
*/
package org.zoodb.internal.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.zoodb.api.impl.ZooPC;
import org.zoodb.internal.GenericObject;
import org.zoodb.internal.Node;
import org.zoodb.internal.ZooClassDef;
import org.zoodb.internal.ZooClassProxy;
import org.zoodb.internal.ZooFieldDef;
import org.zoodb.internal.client.session.ClientSessionCache;
import org.zoodb.internal.server.index.SchemaIndex;
import org.zoodb.internal.util.DBLogger;
import org.zoodb.schema.ZooClass;
/**
* This class maps schema data between the external Schema/ISchema classes and
* the internal ZooClassDef class.
*
* @author Tilmann Zaeschke
*
*/
public class SchemaManager {
private ClientSessionCache cache;
private final ArrayList<SchemaOperation> ops = new ArrayList<SchemaOperation>();
private final boolean isSchemaAutoCreateMode;
public SchemaManager(ClientSessionCache cache, boolean isSchemaAutoCreateMode) {
this.cache = cache;
this.isSchemaAutoCreateMode = isSchemaAutoCreateMode;
}
public boolean isSchemaDefined(Class<?> cls, Node node) {
if (cls == GenericObject.class) {
throw DBLogger.newFatalInternal("This class cannot be persisted: " + cls.getName());
}
ZooClassDef def = cache.getSchema(cls, node);
if (def == null || def.jdoZooIsDeleted()) {
return false;
}
return true;
}
public boolean isSchemaDefined(String clsName) {
ZooClassDef def = cache.getSchema(clsName);
if (def == null || def.jdoZooIsDeleted()) {
return false;
}
return true;
}
/**
* Checks class and disk for class definition.
* @param cls
* @param node
* @return Class definition, may return null if no definition is found.
*/
private ZooClassDef locateClassDefinition(Class<?> cls, Node node) {
ZooClassDef def = cache.getSchema(cls, node);
if (def == null || def.jdoZooIsDeleted()) {
return null;
}
return def;
}
public ZooClassProxy locateSchema(Class<?> cls, Node node) {
if (node == null) {
node = cache.getSession().getPrimaryNode();
}
ZooClassDef def = locateClassDefinition(cls, node);
//not in cache and not on disk
if (def == null) {
return null;
}
//it should now be in the cache
//return a unique handle, even if called multiple times. There is currently
//no real reason, other than that it allows == comparison.
return def.getVersionProxy();
}
public void refreshSchema(ZooClassDef def) {
def.jdoZooGetNode().refreshSchema(def);
def.getProvidedContext().getIndexer().refreshWithSchema(def);
}
/**
* This is only intended to read updates wrt attr-indexes. This can't refresh the schema
* properly (remove remotely deleted schemas, ...). I.e. the cache is NOT updated.
*/
public void refreshSchemaAll() {
//TODO we don't have a database lock here!
for (ZooClassDef def: cache.getSchemata()) {
def.jdoZooGetNode().refreshSchema(def);
def.getProvidedContext().getIndexer().refreshWithSchema(def);
}
}
public ZooClassProxy locateSchema(String className) {
ZooClassDef def = cache.getSchema(className);
if (def == null || def.jdoZooIsDeleted()) {
//not in cache and not on disk || return null if deleted
return null;
}
return getSchemaProxy(def);
}
public ZooClassProxy locateSchemaForObject(long oid, Node node) {
ZooPC pc = cache.findCoByOID(oid);
if (pc != null) {
return pc.jdoZooGetClassDef().getVersionProxy();
}
//object not loaded or instance of virtual class
//so we don't fully load the object, but only get its schema
ZooClassDef def = cache.getSchema(node.getSchemaForObject(oid));
return getSchemaProxy(def);
}
private ZooClassProxy getSchemaProxy(ZooClassDef def) {
//return a unique handle, even if called multiple times. There is currently
//no real reason, other than that it allows == comparison.
return def.getVersionProxy();
}
public ZooClassProxy createSchema(Node node, Class<?> cls) {
if (node == null) {
node = cache.getSession().getPrimaryNode();
}
if (isSchemaDefined(cls, node)) {
throw DBLogger.newUser("Schema is already defined: " + cls.getName());
}
//Is this PersistentCapanbleImpl or a sub class?
if (!(ZooPC.class.isAssignableFrom(cls))) {
throw DBLogger.newUser("Class has no persistent capable super class: " + cls.getName());
}
if (cls.isMemberClass()) {
System.err.println("ZooDB - Found innner class: " + cls.getName());
throw DBLogger.newUser(
"Member (non-static inner) classes are not permitted: " + cls.getName());
}
if (cls.isLocalClass()) {
throw DBLogger.newUser(
"Local classes (defined in a method) are not permitted: " + cls.getName());
}
if (cls.isAnonymousClass()) {
throw DBLogger.newUser("Anonymous classes are not permitted: " + cls.getName());
}
if (cls.isInterface()) {
throw DBLogger.newUser("Interfaces are currently not supported: " + cls.getName());
}
ZooClassDef def;
if (cls != ZooPC.class) {
Class<?> clsSuper = cls.getSuperclass();
ZooClassDef defSuper = locateClassDefinition(clsSuper, node);
if (defSuper == null && isSchemaAutoCreateMode) {
defSuper = createSchema(node, clsSuper).getSchemaDef();
}
def = ZooClassDef.createFromJavaType(cls, defSuper, node, cache.getSession());
} else {
def = ZooClassDef.createFromJavaType(cls, null, node, cache.getSession());
}
cache.addSchema(def, false, node);
ops.add(new SchemaOperation.SchemaDefine(def));
return def.getVersionProxy();
}
public void deleteSchema(ZooClassProxy proxy, boolean deleteSubClasses) {
if (!deleteSubClasses && !proxy.getSubProxies().isEmpty()) {
throw DBLogger.newUser("Cannot remove class schema while sub-classes are " +
" still defined: " + proxy.getSubProxies().get(0).getName());
}
if (proxy.getSchemaDef().jdoZooIsDeleted()) {
throw DBLogger.newObjectNotFoundException("This objects has already been deleted.");
}
if (deleteSubClasses) {
while (!proxy.getSubProxies().isEmpty()) {
deleteSchema(proxy.getSubProxies().get(0), true);
}
}
//delete instances
for (ZooPC pci: cache.getAllObjects()) {
if (pci.jdoZooGetClassDef().getSchemaId() == proxy.getSchemaId()) {
pci.jdoZooMarkDeleted();
}
}
for (GenericObject go: cache.getAllGenericObjects()) {
if (go.jdoZooGetClassDef().getSchemaId() == proxy.getSchemaId()) {
go.jdoZooMarkDeleted();
}
}
// Delete whole version tree
ops.add(new SchemaOperation.SchemaDelete(proxy));
for (ZooClassDef def: proxy.getAllVersions()) {
def.jdoZooMarkDeleted();
}
}
public void defineIndex(ZooFieldDef f, boolean isUnique) {
if (f.isIndexed()) {
throw DBLogger.newUser("Field is already indexed: " + f.getName());
}
//Is type indexable?
SchemaIndex.FTYPE.fromType(f);
ops.add(new SchemaOperation.IndexCreate(f, isUnique));
}
public boolean removeIndex(ZooFieldDef f) {
if (!f.isIndexed()) {
return false;
}
ops.add(new SchemaOperation.IndexRemove(f));
return true;
}
public boolean isIndexDefined(ZooFieldDef f) {
return f.isIndexed();
}
public boolean isIndexUnique(ZooFieldDef f) {
if (!f.isIndexed()) {
throw DBLogger.newUser("Field has no index: " + f.getName());
}
return f.isIndexUnique();
}
public void commit() {
//If nothing changed, there is no need to verify anything!
if (ops.isEmpty()) {
return;
}
Set<String> missingSchemas = new HashSet<String>();
//loop until all schemas are auto-defined (if in auto-mode)
do {
missingSchemas.clear();
Collection<ZooClassDef> schemata = cache.getSchemata();
for (ZooClassDef cs: schemata) {
if (cs.jdoZooIsDeleted()) continue;
//check ALL classes, e.g. to find references to removed classes
checkSchemaFields(cs, schemata, missingSchemas);
}
addMissingSchemas(missingSchemas);
} while (!missingSchemas.isEmpty());
// perform pending operations
for (SchemaOperation op: ops) {
op.commit();
}
}
public void postCommit() {
ops.clear();
}
/**
* This method add all schemata that were found missing when checking all known
* schemata.
* @param missingSchemas
*/
private void addMissingSchemas(Set<String> missingSchemas) {
if (missingSchemas.isEmpty()) {
return;
}
for (String className: missingSchemas) {
Class<?> cls;
try {
cls = Class.forName(className);
} catch (ClassNotFoundException e) {
throw DBLogger.newFatal("Invalid field type in schema", e);
}
//TODO primary node is not always right here...
createSchema(cache.getSession().getPrimaryNode(), cls);
}
}
/**
* Check the fields defined in this class.
* @param schema
* @param missingSchemas
* @param schemata
*/
private void checkSchemaFields(ZooClassDef schema, Collection<ZooClassDef> cachedSchemata,
Set<String> missingSchemas) {
//do this only now, because only now we can check which field types
//are really persistent!
//TODO check for field types that became persistent only now -> error!!
//--> requires schema evolution.
schema.associateFCOs(cachedSchemata, isSchemaAutoCreateMode, missingSchemas);
// TODO:
// - construct fieldDefs here an give them to classDef.
// - load required field type defs
// - check cache (the cachedList only contains dirty/new schemata!)
}
public void rollback() {
// undo pending operations - to be rolled back in reverse order
for (int i = ops.size()-1; i >= 0; i--) {
ops.get(i).rollback();
}
ops.clear();
}
public Object dropInstances(ZooClassProxy def) {
ops.add(new SchemaOperation.DropInstances(def));
return true;
}
public void renameSchema(ZooClassDef def, String newName) {
if (cache.getSchema(newName) != null) {
throw new IllegalStateException("Class name is already in use: " + newName);
}
ops.add(new SchemaOperation.SchemaRename(cache, def, newName));
}
public Collection<ZooClass> getAllSchemata() {
ArrayList<ZooClass> list = new ArrayList<ZooClass>();
for (ZooClassDef def: cache.getSchemata()) {
if (!def.jdoZooIsDeleted()) {
list.add( getSchemaProxy(def) );
}
}
return list;
}
public ZooClass declareSchema(String className, ZooClass superCls) {
if (isSchemaDefined(className)) {
throw new IllegalArgumentException("This class is already defined: " + className);
}
Node node = cache.getSession().getPrimaryNode();
long oid = node.getOidBuffer().allocateOid();
ZooClassDef defSuper;
if (superCls != null) {
defSuper = ((ZooClassProxy)superCls).getSchemaDef();
} else {
defSuper = locateClassDefinition(ZooPC.class, node);
}
ZooClassDef def = ZooClassDef.declare(className, oid, defSuper.getOid());
def.associateSuperDef(defSuper);
def.associateProxy(new ZooClassProxy(def, cache.getSession()));
def.associateFields();
cache.addSchema(def, false, node);
ops.add(new SchemaOperation.SchemaDefine(def));
return def.getVersionProxy();
}
public ZooFieldDef addField(ZooClassDef def, String fieldName, Class<?> type) {
def = def.getModifiableVersion(cache, ops);
long fieldOid = def.jdoZooGetNode().getOidBuffer().allocateOid();
ZooFieldDef field = ZooFieldDef.create(def, fieldName, type, fieldOid);
applyOp(new SchemaOperation.SchemaFieldDefine(def, field), def);
return field;
}
public ZooFieldDef addField(ZooClassDef def, String fieldName, ZooClassDef typeDef,
int arrayDim) {
def = def.getModifiableVersion(cache, ops);
ZooFieldDef field = ZooFieldDef.create(def, fieldName, typeDef, arrayDim);
applyOp(new SchemaOperation.SchemaFieldDefine(def, field), def);
return field;
}
public ZooClassDef removeField(ZooFieldDef field) {
ZooClassDef def = field.getDeclaringType().getModifiableVersion(cache, ops);
//new version -- new field
field = def.getField(field.getName());
applyOp(new SchemaOperation.SchemaFieldDelete(def, field), def);
return def;
}
/**
* Apply an operation to all objects in the cache.
* @param op
* @param def
*/
private void applyOp(SchemaOperation op, ZooClassDef def) {
ops.add(op);
for (GenericObject o: cache.getAllGenericObjects()) {
if (o.jdoZooGetClassDef() == def) {
o.evolve(op);
}
}
}
public void renameField(ZooFieldDef field, String fieldName) {
//We do not create a new version just for renaming.
ZooClassDef def = field.getDeclaringType();
ops.add(new SchemaOperation.SchemaFieldRename(field, fieldName));
def.jdoZooMarkDirty();
}
public boolean getAutoCreateSchema() {
return isSchemaAutoCreateMode;
}
public boolean hasChanges() {
return !ops.isEmpty();
}
}