/* * 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.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import javax.jdo.ObjectState; import org.zoodb.api.impl.ZooPC; import org.zoodb.internal.client.AbstractCache; import org.zoodb.internal.client.SchemaOperation; import org.zoodb.internal.client.SchemaOperation.SchemaFieldDefine; import org.zoodb.internal.client.SchemaOperation.SchemaFieldDelete; import org.zoodb.internal.server.ObjectReader; import org.zoodb.internal.server.index.BitTools; import org.zoodb.internal.util.DBLogger; import org.zoodb.internal.util.Util; import org.zoodb.tools.internal.ObjectCache.GOProxy; /** * Instances of this class represent persistent instances that cannot be de-serialised into * Java classes, because according Java classes do not exist. This can for example occur during * schema evolution. * * Generating required classes is not always possible, because there may be a class with the * required name but with a wrong set of attributes. * TODO a solution would be to generate temporary Java classes for de-serialisation, using * a temporary name such as Zoo__Generic__[ClassName]. * * Since we have to insert/delete attributes, we need to split them up * (otherwise, just mark them as deleted? how about inserted???). * * Uses: * - One aim is to return a bitstream with the correct schema (transport to client) * - Store locally with updated schema -> requires updated bit-stream * - Alternatively, allow reading as if the schema was modified (through transparent mapping)??? * * What should the output be: * - A bitstream: Can be transferred to client (or server), can be directly stored or fed to * DeSerialiser. * - A generic object that allows random access? This could also be achieved by using a bitstream * and the NoClass(De)Serializer. * * Output: BIT-STREAM! * TODO rename to SchemaMapper? Input: byte[]; output: evolved byte[] * * However, byte[] is not ideal for random access (updates). How do we do updates? Updates * require resizing (e.g. String). This could be done on-the-fly on a byte[] or afterwards * using an existing Serializer (?); not quite, an existing Serializer takes real objects as input, * not serialised ones (important for SCOs?!?!?). * * * How is deserialisation different? * - Assigning values: * - values are assigned to an Object[] instead of Field instances. This affects only * readPrimitive() and deserializeFields1()/2(). * - References: * - FCOs (hollowToObject()) returns a Long instead of an PCImpl * - SCOs return a byte[] instead of Object. * * --> CHECK Looks like we could integrate thus into the existing deserializer... * * This is clear: * - Input: Bit-Stream, SchemaDefinition, SchemaMapping * - Capability: Mapping (on-the-fly or permanent evolution) of schemata. * - Output: ? * * - Input #2: Updates to fields * - Output #2: Individual fields * - I/O #2: Could be primitives, but also byte[] for SCOs!!!! * * @author Tilmann Zaschke */ public class GenericObject extends ZooPC { private ZooClassDef defCurrent; private ZooClassDef defOriginal; //TODO keep in single ba[]?!?!? private Object[] fixedValues; private Object[] variableValues; private boolean isDbCollection = false; //== instanceof DBCollection private Object dbCollectionData; private ZooHandleImpl handle = null; private GenericObject(ObjectState state, ZooClassDef def, long oid, boolean isNew, AbstractCache cache) { jdoZooInit(state, def.getProvidedContext(), oid); this.defCurrent = def; this.defOriginal = def; fixedValues = new Object[def.getAllFields().length]; variableValues = new Object[def.getAllFields().length]; cache.addGeneric(this); if (isNew) { jdoZooSetTimestamp(def.getProvidedContext().getSession().getTransactionId()); } } public static GenericObject newInstance(ZooClassDef def, long oid, boolean isNewAndDirty, AbstractCache cache) { GenericObject go = new GenericObject(ObjectState.HOLLOW_PERSISTENT_NONTRANSACTIONAL, def, oid, isNewAndDirty, cache); return go; } /** * Creates new instances. * @param def * @return A new empty generic object. */ static GenericObject newEmptyInstance(ZooClassDef def, AbstractCache cache) { long oid = def.getProvidedContext().getNode().getOidBuffer().allocateOid(); return newEmptyInstance(oid, def, cache); } static GenericObject newEmptyInstance(long oid, ZooClassDef def, AbstractCache cache) { def.getProvidedContext().getNode().getOidBuffer().ensureValidity(oid); GenericObject go = new GenericObject(ObjectState.PERSISTENT_NEW, def, oid, true, cache); //initialise //We do not use default values here, because we are not evolving objects (is that a //good reason?) for (ZooFieldDef f: def.getAllFields()) { if (f.isPrimitiveType()) { Object x; switch (f.getPrimitiveType()) { case BOOLEAN: x = false; break; case BYTE: x = (byte)0; break; case CHAR: x = (char)0; break; case DOUBLE: x = (double)0; break; case FLOAT: x = (float)0; break; case INT: x = (int)0; break; case LONG: x = (long)0; break; case SHORT: x = (short)0; break; default: throw new IllegalStateException(); } go.setField(f, x); } else if (f.isString()) { go.setField(f, null); } } return go; } public void setFieldRAW(int i, Object deObj) { fixedValues[i] = deObj; } public Object getFieldRaw(int i) { return fixedValues[i]; } public void setFieldRawSCO(int i, Object deObj) { variableValues[i] = deObj; if (deObj instanceof String) { fixedValues[i] = BitTools.toSortableLong((String)deObj); } } public Object getFieldRawSCO(int i) { return variableValues[i]; } public void setField(ZooFieldDef fieldDef, Object val) { int i = fieldDef.getFieldPos(); switch (fieldDef.getJdoType()) { case ARRAY: case BIG_DEC: case BIG_INT: case NUMBER: throw new UnsupportedOperationException(); case DATE: fixedValues[i] = ((Date)val).getTime(); variableValues[i] = val; break; case PRIMITIVE: try { //this ensures the correct type Object x; switch (fieldDef.getPrimitiveType()) { case BOOLEAN: x = val; break; case BYTE: x = (Byte)val; break; case CHAR: x = (Character)val; break; case DOUBLE: x = (Double)val; break; case FLOAT: x = (Float)val; break; case INT: x = (Integer)val; break; case LONG: x = (Long)val; break; case SHORT: x = (Short)val; break; default: throw new IllegalStateException(); } fixedValues[i] = x; break; } catch (ClassCastException e) { throw new IllegalArgumentException("Value is of wrong type. Expected " + fieldDef.getPrimitiveType() + " but was " + val.getClass()); } case REFERENCE: if (val != null) { if (val instanceof ZooPC) { fixedValues[i] = ((ZooPC)val).jdoZooGetOid(); } else if (val instanceof GenericObject) { fixedValues[i] = ((GenericObject)val).getOid(); } else if (val instanceof GOProxy) { fixedValues[i] = ((GOProxy)val).go.getOid(); } else if (val instanceof ZooHandleImpl) { val = ((ZooHandleImpl)val).getGenericObject(); fixedValues[i] = ((GenericObject)val).getOid(); } else { throw DBLogger.newUser("Illegal argument type: " + val.getClass()); } } variableValues[i] = val; break; case SCO: throw new UnsupportedOperationException(); case STRING: fixedValues[i] = BitTools.toSortableLong((String)val); variableValues[i] = val; break; } } /** * * Returns OIDs for references. * * @param fieldDef * @return The value of that field. */ public Object getField(ZooFieldDef fieldDef) { int i = fieldDef.getFieldPos(); switch (fieldDef.getJdoType()) { case ARRAY: case BIG_DEC: case BIG_INT: return variableValues[i]; case DATE: return new Date((Long)fixedValues[i]); case NUMBER: if (fixedValues[i] == null) { return null; } //return fixedValues[i]; throw new UnsupportedOperationException(fieldDef.getTypeName()); case PRIMITIVE: return fixedValues[i]; case REFERENCE: return variableValues[i]; case SCO: case STRING: return variableValues[i]; default: throw new IllegalArgumentException(); } } public ZooClassDef ensureLatestVersion() { while (defCurrent.getNextVersion() != null) { evolve(); } return defCurrent; } private ZooClassDef evolve() { //TODO this is horrible!!! ArrayList<Object> fV = new ArrayList<Object>(Arrays.asList(fixedValues)); ArrayList<Object> vV = new ArrayList<Object>(Arrays.asList(variableValues)); //TODO resize only once to correct size for (PersistentSchemaOperation op: jdoZooGetClassDef().getNextVersion().getEvolutionOps()) { if (op.isAddOp()) { fV.add(op.getFieldId(), op.getInitialValue()); vV.add(op.getFieldId(), null); } else { fV.remove(op.getFieldId()); vV.remove(op.getFieldId()); } } fixedValues = fV.toArray(fixedValues); variableValues = vV.toArray(variableValues); defCurrent = defCurrent.getNextVersion(); //We call this just to set the context/ZooClassDef. The state can be ignored since //it is only a temporary object and not in the cache jdoZooInit(ObjectState.PERSISTENT_DIRTY, defCurrent.getProvidedContext(), jdoZooGetOid()); return defCurrent; } /** * Schema evolution of in-memory objects, operation by operation. * @param op */ public void evolve(SchemaOperation op) { //TODO this is horrible!!! ArrayList<Object> fV = new ArrayList<Object>(Arrays.asList(fixedValues)); ArrayList<Object> vV = new ArrayList<Object>(Arrays.asList(variableValues)); //TODO resize only once to correct size if (op instanceof SchemaOperation.SchemaFieldDefine) { SchemaOperation.SchemaFieldDefine op2 = (SchemaFieldDefine) op; int fieldId = op2.getField().getFieldPos(); fV.add(fieldId, PersistentSchemaOperation.getDefaultValue(op2.getField())); vV.add(fieldId, null); } if (op instanceof SchemaOperation.SchemaFieldDelete) { SchemaOperation.SchemaFieldDelete op2 = (SchemaFieldDelete) op; int fieldId = op2.getField().getFieldPos(); fV.remove(fieldId); vV.remove(fieldId); } fixedValues = fV.toArray(fixedValues); variableValues = vV.toArray(variableValues); } public ObjectReader toStream() { System.err.println("FIXME: Size of generic object writer"); //TODO, this is so dirty... If the buffer is to small, we retry with a bigger one int size = 1000; ByteBuffer ba = null; while (ba == null) { try { GenericObjectWriter gow = new GenericObjectWriter(size, jdoZooGetClassDef().getOid(), jdoZooGetTimestamp()); gow.newPage(); DataSerializer ds = new DataSerializer(gow, jdoZooGetContext().getSession().internalGetCache(), jdoZooGetNode()); ds.writeObject(this, jdoZooGetClassDef()); ba = gow.toByteArray(); } catch (BufferOverflowException e) { //ignore, hehe... size = size * 10; System.err.println("FIXME: Resizing buffer!!!! " + size); } } ba.rewind(); return new ObjectReader(new GenericObjectReader(ba)); } public long getOid() { return jdoZooGetOid(); } public void setOid(long oid) { jdoZooSetOid(oid); } public ZooClassDef getClassDefCurrent() { return defCurrent; } public ZooClassDef getClassDefOriginal() { return defOriginal; } public void setClassDefOriginal(ZooClassDef defOriginal) { this.defOriginal = defOriginal; } public boolean isDbCollection() { return isDbCollection; } /** * This is used to represent DBCollection objects as GenericObjects. * @param collection */ public void setDbCollection(Object collection) { if (collection != null) { dbCollectionData = collection; isDbCollection = true; } else { dbCollectionData = null; isDbCollection = false; } } public Object getDbCollection() { return dbCollectionData; } @Override public boolean equals(Object o) { if (o == null || !(o instanceof GenericObject)) { return false; } System.out.println("FIXME: compare values"); return true; } public ZooHandleImpl getOrCreateHandle() { if (handle == null) { handle = new ZooHandleImpl(this, defOriginal.getVersionProxy()); } return handle; } public void activateRead() { //TODO use ZooDB method?? if (jdoZooIsStateHollow()) { GenericObject go = jdoZooGetNode().readGenericObject( jdoZooGetClassDef(), jdoZooGetOid()); if (go != this) { throw DBLogger.newFatalInternal("Arrgh!"); } } } /** * This method verifies that this GO has no PCI representation or that the PCI representation * is not dirty or new. * Otherwise it will throw an exception in order to prevent the dirty state of the GO and the * PC to result in conflicting updates in the database. */ void verifyPcNotDirty() { if (handle == null || handle.internalGetPCI() == null || !handle.internalGetPCI().jdoZooIsDirty()) { ZooPC pc = jdoZooGetContext().getSession().internalGetCache().findCoByOID( jdoZooGetOid()); if (pc == null || !pc.jdoZooIsDirty()) { return; } } throw DBLogger.newUser("This object has been modified via its Java class as well as via" + " the schema API. This is not allowed. Objectid: " + Util.oidToString(jdoZooGetOid())); } /** * @return True if there is an associated PC that is deleted. */ public boolean checkPcDeleted() { if (handle == null || handle.internalGetPCI() == null || !handle.internalGetPCI().jdoZooIsDeleted()) { ZooPC pc = jdoZooGetContext().getSession().internalGetCache().findCoByOID( jdoZooGetOid()); if (pc == null || !pc.jdoZooIsDeleted()) { return false; } } return true; } public Object[] getRawFields() { return fixedValues; } public void invalidate() { handle.invalidate(); } }