/*
* 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;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.zoodb.api.impl.ZooPC;
import org.zoodb.internal.client.SchemaManager;
import org.zoodb.internal.util.ClassCreator;
import org.zoodb.internal.util.DBLogger;
import org.zoodb.internal.util.DBTracer;
import org.zoodb.internal.util.IteratorTypeAdapter;
import org.zoodb.internal.util.Util;
import org.zoodb.schema.ZooClass;
import org.zoodb.schema.ZooField;
import org.zoodb.schema.ZooHandle;
/**
* Internal Schema class.
*
* This should not throw any JDOxyz-exceptions, because it is not part of the JDO API.
*
* The class serves as a proxy for the latest version of a particular class in the schema version
* tree.
* The proxy's reference to the latest version is updated by SchemaOperations.
*
* @author Tilmann Zaeschke
*/
public class ZooClassProxy implements ZooClass {
private ZooClassDef def;
private final ZooClassProxy superProxy;
private final SchemaManager schemaManager;
private final long schemaId;
private ArrayList<ZooClassProxy> subClasses = new ArrayList<ZooClassProxy>();
private final Session session;
private boolean isValid = true;
public ZooClassProxy(ZooClassDef def, Session session) {
this.def = def;
this.schemaManager = session.getSchemaManager();
this.schemaId = def.getSchemaId();
this.session = session;
ZooClassDef defSuper = def.getSuperDef();
if (!def.getClassName().equals(ZooPC.class.getName())) {
if (defSuper.getVersionProxy() == null) {
//super-class needs a proxy
this.superProxy = new ZooClassProxy(defSuper, session);
defSuper.associateProxy(superProxy);
} else {
//super class already has a proxy
this.superProxy = defSuper.getVersionProxy();
}
this.superProxy.subClasses.add(this);
//associate previous versions
while ((def = def.getPreviousVersion()) != null) {
def.associateProxy(this);
}
} else {
// this is the root class
superProxy = null;
}
}
@Override
public void remove() {
DBTracer.logCall(this);
checkInvalidWrite();
schemaManager.deleteSchema(this, false);
}
@Override
public void removeWithSubClasses() {
DBTracer.logCall(this);
checkInvalidWrite();
schemaManager.deleteSchema(this, true);
}
public ZooClassDef getSchemaDef() {
DBTracer.logCall(this);
checkInvalidRead();
return def;
}
protected void checkInvalidWrite() {
checkInvalid(true);
}
protected void checkInvalidRead() {
checkInvalid(false);
}
protected void checkInvalid(boolean write) {
if (!isValid) {
throw new IllegalStateException("This schema has been invalidated.");
}
if (session.isClosed()) {
throw new IllegalStateException("This schema belongs to a closed PersistenceManager.");
}
//TODO For READ we do NOT check for an active transaction here. Reasons:
//1) All schemata should be cached in memory. We only need an active transaction
// for refreshing schemata.
//2) JDO has many operations that do not require an active transaction, such as creating
// a new Query. Conceptually, these belong to the PM, which has no 'active' state.
//BUT: For now we just fail. We would need to distinguish weather we need schema-read or
// object-read (getHandleIterator())
if (!session.isActive()) {
if (write || !session.getConfig().getNonTransactionalRead()) {
throw new IllegalStateException("The transaction is currently not active.");
}
}
if (def.jdoZooIsDeleted()) {
throw new IllegalStateException("This schema object is invalid, for " +
"example because it has been deleted.");
}
//consistency check
if (def.getNextVersion() != null) {
throw new IllegalStateException();
}
}
@Override
public void createIndex(String fieldName, boolean isUnique) {
DBTracer.logCall(this, fieldName, isUnique);
checkInvalidWrite();
locateFieldOrFail(fieldName).createIndex(isUnique);
}
@Override
public boolean removeIndex(String fieldName) {
DBTracer.logCall(this, fieldName);
checkInvalidWrite();
return locateFieldOrFail(fieldName).removeIndex();
}
@Override
public boolean hasIndex(String fieldName) {
DBTracer.logCall(this, fieldName);
checkInvalidRead();
return locateFieldOrFail(fieldName).hasIndex();
}
@Override
public boolean isIndexUnique(String fieldName) {
DBTracer.logCall(this, fieldName);
checkInvalidRead();
return locateFieldOrFail(fieldName).isIndexUnique();
}
private ZooField locateFieldOrFail(String fieldName) {
ZooField f = getField(fieldName);
if (f == null) {
throw new IllegalArgumentException("Field not found: " + fieldName);
}
return f;
}
@Override
public void dropInstances() {
DBTracer.logCall(this);
checkInvalidWrite();
schemaManager.dropInstances(this);
}
@Override
public void rename(String newName) {
DBTracer.logCall(this, newName);
checkInvalidWrite();
schemaManager.renameSchema(def, newName);
}
@Override
public String getName() {
DBTracer.logCall(this);
checkInvalidRead();
return def.getClassName();
}
@Override
public ZooClass getSuperClass() {
DBTracer.logCall(this);
checkInvalidRead();
if (def.getSuperDef() == null) {
return null;
}
return def.getSuperDef().getVersionProxy();
}
@Override
public List<ZooField> getAllFields() {
DBTracer.logCall(this);
checkInvalidRead();
ArrayList<ZooField> ret = new ArrayList<ZooField>();
for (ZooFieldDef fd: def.getAllFields()) {
ret.add(fd.getProxy());
}
return ret;
}
@Override
public List<ZooField> getLocalFields() {
DBTracer.logCall(this);
checkInvalidRead();
ArrayList<ZooField> ret = new ArrayList<ZooField>();
for (ZooFieldDef fd: def.getAllFields()) {
if (fd.getDeclaringType() == def) {
ret.add(fd.getProxy());
}
}
return ret;
}
@Override
public ZooField addField(String fieldName, Class<?> type) {
DBTracer.logCall(this, fieldName, type);
checkAddField(fieldName);
ZooFieldDef field = schemaManager.addField(def, fieldName, type);
//Update, in case it changed
def = field.getDeclaringType();
return field.getProxy();
}
@Override
public ZooField addField(String fieldName, ZooClass type, int arrayDepth) {
DBTracer.logCall(this, fieldName, type, arrayDepth);
checkAddField(fieldName);
ZooClassDef typeDef = ((ZooClassProxy)type).getSchemaDef();
ZooFieldDef field = schemaManager.addField(def, fieldName, typeDef, arrayDepth);
//Update, in case it changed
def = field.getDeclaringType();
return field.getProxy();
}
private void checkAddField(String fieldName) {
checkInvalidWrite();
checkJavaFieldNameConformity(fieldName);
//check existing names
for (ZooFieldDef fd: def.getAllFields()) {
if (fd.getName().equals(fieldName)) {
throw new IllegalArgumentException("Field name already defined: " + fieldName);
}
}
}
static void checkJavaFieldNameConformity(String fieldName) {
if (fieldName == null || fieldName.length() == 0) {
throw new IllegalArgumentException("Field name invalid: '" + fieldName + "'");
}
for (int i = 0; i < fieldName.length(); i++) {
char c = fieldName.charAt(i);
if (i == 0) {
if (!Character.isJavaIdentifierStart(c)) {
throw new IllegalArgumentException("Field name invalid: " + fieldName);
}
} else {
if (!Character.isJavaIdentifierPart(c)) {
throw new IllegalArgumentException("Field name invalid: " + fieldName);
}
}
}
}
@Override
public String toString() {
checkInvalidRead();
return "Class schema(" + Util.getOidAsString(def) + "): " + def.getClassName();
}
@Override
public Class<?> getJavaClass() {
DBTracer.logCall(this);
Class<?> cls = def.getJavaClass();
if (cls == null) {
try {
cls = Class.forName(def.getClassName());
} catch (ClassNotFoundException e) {
cls = ClassCreator.createClass(
def.getClassName(), def.getSuperDef().getClassName());
//throw new JDOUserException("Class not found: " + className, e);
}
}
return cls;
}
@Override
public ZooField getField(String fieldName) {
DBTracer.logCall(this, fieldName);
checkInvalidRead();
for (ZooFieldDef f: def.getAllFields()) {
if (f.getName().equals(fieldName)) {
return f.getProxy();
}
}
return null;
}
@Override
public void removeField(String fieldName) {
DBTracer.logCall(this, fieldName);
checkInvalidWrite();
ZooField f = getField(fieldName);
if (f == null) {
throw new IllegalStateException(
"Field not found: " + def.getClassName() + "." + fieldName);
}
removeField(f);
}
@Override
public void removeField(ZooField field) {
DBTracer.logCall(this, field);
checkInvalidWrite();
ZooFieldDef fieldDef = ((ZooFieldProxy)field).getFieldDef();
def = schemaManager.removeField(fieldDef);
}
public void newVersionRollback(ZooClassDef newDef) {
if (def.getSchemaId() != schemaId) {
//this would indicate a bug
throw new IllegalArgumentException();
}
if (def != newDef
|| newDef.getPreviousVersion() == null
|| newDef.getNextVersion() != null) {
//this would indicate a bug
throw new IllegalStateException();
}
def = newDef.getPreviousVersion();
}
public void newVersion(ZooClassDef newDef) {
if (def.getSchemaId() != schemaId) {
//this would indicate a bug
throw new IllegalArgumentException();
}
if (def == newDef
|| newDef.getPreviousVersion() != def
|| newDef.getNextVersion() != null) {
//this would indicate a bug
throw new IllegalStateException();
}
def = newDef;
}
@Override
public List<ZooClass> getSubClasses() {
DBTracer.logCall(this);
checkInvalidRead();
ArrayList<ZooClass> subs = new ArrayList<ZooClass>();
for (ZooClassProxy sub: subClasses) {
subs.add(sub);
}
return subs;
}
@Override
public Iterator<?> getInstanceIterator() {
DBTracer.logCall(this);
checkInvalidRead();
return def.jdoZooGetNode().loadAllInstances(this, true);
}
@Override
public Iterator<ZooHandle> getHandleIterator(boolean subClasses) {
DBTracer.logCall(this, subClasses);
checkInvalidRead();
return new IteratorTypeAdapter<ZooHandle>(
def.jdoZooGetNode().oidIterator(this, subClasses));
}
public ArrayList<ZooClassDef> getAllVersions() {
ArrayList<ZooClassDef> ret = new ArrayList<ZooClassDef>();
ZooClassDef d = def;
while (d != null) {
ret.add(d);
d = d.getPreviousVersion();
}
return ret;
}
public List<ZooClassProxy> getSubProxies() {
return subClasses;
}
/**
*
* @return Schema ID which is independent of the schema version.
*/
public long getSchemaId() {
return schemaId;
}
/**
* Schema operation callback for removing this class definition.
*/
public void socRemoveDef() {
if (!superProxy.subClasses.remove(this)) {
throw DBLogger.newFatalInternal("Schema structure is inconsistent.");
}
}
/**
* Schema operation callback for removing this class definition.
*/
public void socRemoveDefRollback() {
superProxy.subClasses.add(this);
}
@Override
public long instanceCount(boolean subClasses) {
DBTracer.logCall(this, subClasses);
checkInvalidRead();
if (def.jdoZooIsNew() && def.getSchemaVersion() == 0) {
return 0;
}
return def.jdoZooGetNode().countInstances(this, subClasses);
}
@Override
public ZooHandle newInstance() {
DBTracer.logCall(this);
GenericObject go = GenericObject.newEmptyInstance(def, session.internalGetCache());
ZooHandleImpl hdl = go.getOrCreateHandle();
return hdl;
}
@Override
public ZooHandle newInstance(long oid) {
DBTracer.logCall(this, oid);
if (session.isOidUsed(oid)) {
throw new IllegalArgumentException(
"An object with this OID already exists: " + Util.oidToString(oid));
}
GenericObject go = GenericObject.newEmptyInstance(oid, def, session.internalGetCache());
ZooHandleImpl hdl = go.getOrCreateHandle();
return hdl;
}
public void invalidate() {
isValid = false;
//in case the fields were create through a Java class (in stead of schema operations)
//we need to invalidate them from here
for (ZooFieldDef f: def.getAllFields()) {
f.getProxy().invalidate();
}
}
}