/**
* Copyright Mobixess Inc. 2007
*/
package com.mobixess.jodb.core.transaction;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.mobixess.jodb.core.IllegalClassTypeException;
import com.mobixess.jodb.core.JODBConfig;
import com.mobixess.jodb.core.JodbIOException;
import com.mobixess.jodb.core.index.IndexingRecord;
import com.mobixess.jodb.core.index.JODBIndexingRootAgent;
import com.mobixess.jodb.core.io.IOTicket;
import com.mobixess.jodb.core.io.IRandomAccessDataBuffer;
import com.mobixess.jodb.core.io.IOBase;
import com.mobixess.jodb.core.io.JODBIOBase;
import com.mobixess.jodb.core.io.JODBOperationContext;
import com.mobixess.jodb.core.io.ObjectDataContainer;
import com.mobixess.jodb.core.io.ObjectDataContainer.FieldsIterator;
import com.mobixess.jodb.core.plugin.IClassProcessor;
import com.mobixess.jodb.core.plugin.JODBPluginRegistry;
import com.mobixess.jodb.core.transaction.JODBSession.ClassDescriptor;
import com.mobixess.jodb.core.transaction.JODBSession.FieldAndIDRecord;
import com.mobixess.jodb.core.transaction.TransactionUtils.DataContainersCache;
import com.mobixess.jodb.util.PrimitiveJavaTypesUtil;
import com.mobixess.jodb.util.Utils;
/**
* @author Mobixess
*
*/
public class TransactionAssembler {
public final static byte TRANSACTION_REPLACEMENT_ENTRY_TYPE_STATIC = 0xf;
public final static byte TRANSACTION_REPLACEMENT_ENTRY_TYPE_REDIRECTOR = 0x7f;
private static Logger _logger = Utils.getLogger(TransactionAssembler.class.getName());
private final static long MAX_ABSOLUTE_SHORT_ADDR = 0xFFFFFFFFL;
//WARNING: this is hard transaction limit.
private final static long TRANSACTION_SIZE_LIMIT = Math.min(Integer.MAX_VALUE, -Integer.MIN_VALUE)/2;
/**
*
* @param context
* @param tContainer
* @throws IOException
* @throws IllegalClassTypeException
*/
public static void assembleTransactionData(JODBOperationContext context, TransactionContainer tContainer) throws IOException, IllegalClassTypeException{
if(tContainer.isAgentsMode()){
//make sure the root agent processed first
JODBIndexingRootAgent indexingRootAgent = context.getIndexingRootAgent();
if(tContainer.getHandleForObject(indexingRootAgent)!=null){
assembleTransactionDataForObject(context, indexingRootAgent, tContainer);
}
}
Map<Object, TransactionHandle> transactionObjects = tContainer.getTransactionObjects();
Iterator<Object> iter = transactionObjects.keySet().iterator();
while (iter.hasNext() ) {
tContainer.resetTransactionBufferToEnd();
Object element = iter.next();
assembleTransactionDataForObject(context, element, tContainer);
}
}
private static long estimateHeaderAndAuxDataLength(TransactionHandle tHandle, /*int classHierarhyLen,*/ boolean translated){
long lengthEstimate = 0;
lengthEstimate += 3; // mask + version
if(tHandle.generateUID()){
lengthEstimate += 8; // uid
}
if(tHandle.generateCreationTS()){
lengthEstimate += 8; // creation TS
}
if(tHandle.generateModificationTS()){
lengthEstimate += 8; // modification TS
}
lengthEstimate += 2;// class hierarhy
if(translated){
lengthEstimate += 2;
}
// lengthEstimate += 2; //class names counter
// lengthEstimate += 2 * classHierarhyLen;// class hierarhy
return lengthEstimate;
}
private static long estimateObjectLength(TransactionHandle tHandle, /*int classHierarhyLen,*/
int fieldsWithAbsoluteLen, int fieldsWithRelativeLen,
ClassDescriptor classDescriptor, boolean translated) throws JodbIOException{
long lengthEstimate = estimateHeaderAndAuxDataLength(tHandle, translated);
if(fieldsWithAbsoluteLen > 0){
lengthEstimate += 2; // directly addressed fields count
lengthEstimate += (2+8)*fieldsWithAbsoluteLen;// size for fields ids and pointers
}
if(fieldsWithRelativeLen > 0){
lengthEstimate += 2; // inderectly addressed fields count
lengthEstimate += (2+4)*fieldsWithRelativeLen;// size for fields ids and pointers
}
if(classDescriptor != null){
lengthEstimate += 2; // inderectly addressed fields count
lengthEstimate += classDescriptor.getPrimitiveFieldsStorageEstimate(2);
}
return lengthEstimate;
}
private static long estimateArrayObjectLength(JODBOperationContext context, TransactionHandle tHandle, Object array, ClassDescriptor classDescriptor, ByteHolder elementsSizeOutParam, boolean translated) throws JodbIOException{
int arraySize = Array.getLength(array);
long lengthEstimate = estimateObjectLength(tHandle, 0, 0, null, translated);//header
lengthEstimate+= 4; //array length entry
lengthEstimate+= 1; //array's element size entry
if(classDescriptor.isPrimitiveArray()){
elementsSizeOutParam._value = (byte)PrimitiveJavaTypesUtil.getDataOutputWriteLen(classDescriptor.getArrayType());
lengthEstimate+=elementsSizeOutParam._value*arraySize;
}else{
JODBSession session = context.getSession();
TransactionContainer tContainer = context.getTransactionContainer();
long transactionOffset = context.getTransactionOffset();
boolean newArray = tHandle.isNewObject();
elementsSizeOutParam._value = 4;
for (int i = 0; i < arraySize; i++) {
Object value = Array.get(array, i);
if(value == null){
continue;
}
TransactionHandle valueTransactionHandle = tContainer.getHandleForObject(value);
if(valueTransactionHandle == null){
continue;
}
if(newArray){
if(!valueTransactionHandle.isNewObject()){
long valueObjectOffset = valueTransactionHandle.getHandle().getObjectEntryOffset();
if(valueObjectOffset > MAX_ABSOLUTE_SHORT_ADDR && transactionOffset - valueObjectOffset > TRANSACTION_SIZE_LIMIT){
elementsSizeOutParam._value = 8;
break;
}
}
}else{
if(valueTransactionHandle.isNewObject()){
if( transactionOffset > MAX_ABSOLUTE_SHORT_ADDR - TRANSACTION_SIZE_LIMIT ){
elementsSizeOutParam._value = 8;
break;
}
}else{
long valueObjectOffset = valueTransactionHandle.getHandle().getObjectEntryOffset();
if(valueObjectOffset > MAX_ABSOLUTE_SHORT_ADDR){
elementsSizeOutParam._value = 8;
break;
}
}
}
PersistentObjectHandle objectHandle = session.getHandleForActiveObject(value);
if(newArray){
}
if(objectHandle == null){
continue;
}
if(objectHandle.getObjectEntryOffset() > MAX_ABSOLUTE_SHORT_ADDR){
elementsSizeOutParam._value = 8;
break;
}
}
lengthEstimate+=elementsSizeOutParam._value*arraySize+arraySize/8+1;
}
return lengthEstimate;
}
private static int composeEntryID(short entryID, long length){
int lenModifierID = 0;
if(length <= 0xff){
lenModifierID = JODBIOBase.LEN_MODIFIER_BYTE;
}else if(length > 0xFFFF ){
lenModifierID = JODBIOBase.LEN_MODIFIER_LONG;
}
return entryID|lenModifierID;
}
private static void classifyFields(Object object, ClassDescriptor classDescr, Map<Object, TransactionHandle> transactionObjects, Vector<ObjectFieldRecord> fieldsWithAbsoluteAddr,
Vector<ObjectFieldRecord> fieldsWithRelativeAddr,Vector<Field> primitiveFields) throws IOException
{
FieldAndIDRecord[] fields = classDescr.getAllFields();
for (int i = 0; i < fields.length; i++) {
Field next = fields[i]._field;
if(next.getType().isPrimitive()){
primitiveFields.add(next);
continue;
}
Object value;
try {
value = next.get(object);
} catch (Exception e) {
e.printStackTrace();
throw new JodbIOException(e);
}
TransactionHandle childObjectHandle = transactionObjects.get(value);
if(value == null || childObjectHandle == null || childObjectHandle.is_DELETE_Transaction()){
//null fieds may not require
//nullFields.add(next);
}else{
if(!childObjectHandle.isNewObject()){
fieldsWithAbsoluteAddr.add(new ObjectFieldRecord(next, value));
}else{
fieldsWithRelativeAddr.add(new ObjectFieldRecord(next, value));
}
}
}
}
private static void writeEntryLenForID(int id, long length, IRandomAccessDataBuffer dataBuffer) throws IOException{
switch (id&~JODBIOBase.LEN_MODIFIER_EXCLUSION_MASK) {//reserve space for length
case JODBIOBase.LEN_MODIFIER_BYTE:
dataBuffer.writeByte((byte)length);
break;
case JODBIOBase.LEN_MODIFIER_LONG:
dataBuffer.writeLong(length);
break;
default:
dataBuffer.writeShort((short)length);
}
}
private static int formPrimaryObjectMask(int initalMask, boolean fieldsWithAbsoluteAddr, boolean fieldsWithRelativeAddr, boolean primitiveFields, boolean translated, TransactionHandle tHandle, ClassDescriptor classDescr){
if(fieldsWithAbsoluteAddr){
initalMask = ObjectDataContainer.addDirectlyAddressedFieldsBit(initalMask);
}
if(fieldsWithRelativeAddr){
initalMask= ObjectDataContainer.addRelativelyAddressedFieldsID(initalMask);
}
if(primitiveFields){
initalMask = ObjectDataContainer.addPrimitiveFieldsBit(initalMask);
}
if(translated){
initalMask = ObjectDataContainer.addTranslatedBit(initalMask);
}
if(tHandle.generateCreationTS()){
initalMask = ObjectDataContainer.addCreationTSFieldBit(initalMask);
}
if(tHandle.generateModificationTS()){
initalMask = ObjectDataContainer.addModificationTSFieldBit(initalMask);
}
if(classDescr.isArray()){
initalMask = ObjectDataContainer.addArrayIDBit(initalMask);
}
return initalMask;
}
private static int formSecondaryObjectMask(int initalMask, TransactionHandle transactionHandle){
if(transactionHandle.isAgent()){
initalMask = ObjectDataContainer.addAgentBit(initalMask);
}
initalMask = ObjectDataContainer.addCyclicalCounterBit(initalMask);
return initalMask;
}
private static long assembleTransactionDataForObject(JODBOperationContext context, Object rootObject, TransactionContainer tContainer) throws IOException, IllegalClassTypeException{
DataContainersCache dataContainersCache = TransactionUtils.getObjectDataContainerCache();
ObjectDataContainer persistentCopyObjectDataContainer = dataContainersCache.pullObjectDataContainer();
try {
return writeObjects(context, rootObject, tContainer, persistentCopyObjectDataContainer);
} finally {
dataContainersCache.pushObjectDataContainer(persistentCopyObjectDataContainer);
tContainer.fireOnCommitFinished(rootObject, context.getSession());
}
}
/**
*
* @param context
* @param rootObject
* @param tContainer
* @param persistentCopyObjectDataContainer - data container to read already persisted copy if applicable
* @return
* @throws IOException
* @throws IllegalClassTypeException
*/
private static long writeObjects(JODBOperationContext context, Object rootObject, TransactionContainer tContainer, ObjectDataContainer persistentCopyObjectDataContainer) throws IOException, IllegalClassTypeException{
//TODO add verification "fields count" < short
Map<Object, TransactionHandle> transactionObjects = tContainer.getTransactionObjects();
TransactionHandle tHandle = transactionObjects.get(rootObject);
if(tHandle == null){
throw new IOException("transaction handle unavailable");
}
if(!tContainer.isAgentsMode() && tHandle.isAgent()){
return 0;
}
IOTicket ioTicket = context.getIoTicket();
JODBSession session = context.getSession();
if(tHandle.isTranslated()){
return tHandle.getTransactionOffset();
}
tContainer.fireOnCommitStarted(rootObject, context.getSession());
if(tHandle.is_DELETE_Transaction()){
deleteObject(ioTicket, session, tHandle, tContainer);
return -1;
}
IOBase base = ioTicket.getBase();
IClassProcessor classProcessor = JODBPluginRegistry.getInstance().getClassProcessor(rootObject.getClass());
Object objectToPersist = classProcessor.translate(rootObject);
ClassDescriptor classDescr = session.getDescriptorForClass(objectToPersist.getClass());
FieldAndIDRecord[] fields = classDescr.getAllFields();
Vector<IndexingRecord> indexes = null;
Class rootObjectType = rootObject.getClass();
int rootObjectClassID;
if(rootObjectType.isArray()){
rootObjectClassID = base.getOrSetClassTypeSubstitutionID(rootObjectType.getComponentType().getName());
}else{
rootObjectClassID = base.getOrSetClassTypeSubstitutionID(rootObjectType.getName());
indexes = TransactionUtils.getObjectDataContainerCache().pullVector();
JODBIndexingRootAgent indexingAgent = context.getIndexingRootAgent();
indexingAgent.getAgentsForClassId(indexes, rootObjectClassID);
if(indexes.size() == 0){
//no indexes for this class
TransactionUtils.getObjectDataContainerCache().pushVector(indexes);
indexes = null;
}
}
tHandle.setIndexes(indexes);
//String[] classTypes = classDescr.getTypes();
if( checkActiveObjectUnchanged(classProcessor, context, objectToPersist, tHandle, persistentCopyObjectDataContainer, indexes) ){//should not happen in recursive sub call
tHandle.setIndexes(null);//reset indexes info in handle to prevent post processing
long offset = tHandle.getHandle().getObjectEntryOffset();//JODBIOUtils.addAbsoluteOffsetIdentifierBit(tHandle.getHandle().getObjectEntryOffset());
tHandle.setTransactionOffset(offset);
if( classDescr.isArray()){
if(classDescr.isPrimitiveArray()){
return offset;
}
int arrayLen = Array.getLength(objectToPersist);
for (int i = 0; i < arrayLen; i++) {
Object childObj = Array.get(objectToPersist, i);
if(childObj == null || transactionObjects.get(childObj) == null){
continue;
}
assembleTransactionDataForObject(context, childObj, tContainer);
}
} else {
try {
for (int i = 0; i < fields.length; i++) {
Field field = fields[i]._field;
if (field.getType().isPrimitive()) {
continue;
}
Object childObj = field.get(objectToPersist);
if (childObj == null || transactionObjects.get(childObj) == null){
continue;
}
assembleTransactionDataForObject(context, childObj, tContainer);
}
} catch (Exception e) {
e.printStackTrace();
throw new JodbIOException(e);
}
}
return offset;
}
IRandomAccessDataBuffer transactionFile = tContainer.getTransactionNewDataFile();
transactionFile.resetToEnd();
Vector<ObjectFieldRecord> fieldsWithAbsoluteAddr = new Vector<ObjectFieldRecord>();
Vector<ObjectFieldRecord> fieldsWithRelativeAddr = new Vector<ObjectFieldRecord>();
Vector<Field> primitiveFields = new Vector<Field>();
if(!classDescr.isArray()){
classifyFields(objectToPersist, classDescr, transactionObjects, fieldsWithAbsoluteAddr, fieldsWithRelativeAddr, primitiveFields);
}
long objectIDOffset = transactionFile.getCursorOffset();
tHandle.setTransactionOffset(objectIDOffset);
if(JODBConfig.DEBUG){
_logger.info(" >>> Transaction: Object "+rootObject.getClass()+" "+rootObject+" start offset ="+objectIDOffset);
}
boolean translated = rootObject!=objectToPersist;
byte arrayElementSize = 0;
long lengthEstimate;
if(!classDescr.isArray()){
lengthEstimate = estimateObjectLength(tHandle, fieldsWithAbsoluteAddr.size(), fieldsWithRelativeAddr.size(), classDescr, translated);
}else{
ByteHolder byteHolder = new ByteHolder();
lengthEstimate = estimateArrayObjectLength(context, tHandle, objectToPersist, classDescr, byteHolder, translated);
arrayElementSize = byteHolder._value;
}
int objId = composeEntryID( JODBIOBase.ENTRY_OBJECT_ID, lengthEstimate);
int objIdWithRedirectionBit = tHandle.isNewObject()? objId : JODBIOBase.addRedirectedObjectModifier(objId);
transactionFile.writeShort(objIdWithRedirectionBit);
writeEntryLenForID(objId,0,transactionFile);//reserve space for length
long objectBodyOffset = transactionFile.getCursorOffset();
// if(JODBConfig.DEBUG){
// _logger.info("Transaction: Object "+rootObject.getClass()+" "+rootObject+" header len ="+headerLen);
// }
int primaryMask = formPrimaryObjectMask(0, fieldsWithAbsoluteAddr.size()>0, fieldsWithRelativeAddr.size()>0, primitiveFields.size()>0, translated, tHandle, classDescr);;
transactionFile.writeByte(primaryMask);
int secondaryMask = formSecondaryObjectMask(0, tHandle);
transactionFile.writeByte(secondaryMask);
short newCyclicCounter = (short) (tHandle.getCyclicalVersionCounter()+1);
if(newCyclicCounter == 256){
newCyclicCounter = 0;
}
tHandle.setCyclicalVersionCounter(newCyclicCounter);
transactionFile.writeByte(newCyclicCounter);
tHandle.setTranslatedObjectDataMask((byte) primaryMask);
//
if(tHandle.generateUID()){
Random random = new Random();
transactionFile.writeLong(random.nextLong());
}
long time = System.currentTimeMillis();
if(tHandle.generateCreationTS()){
transactionFile.writeLong(time);
}
if(tHandle.generateModificationTS()){
transactionFile.writeLong(time);
}
//
transactionFile.writeShort(rootObjectClassID);
if(translated){
int translatedObjectClassID = base.getOrSetClassTypeSubstitutionID(classDescr.getTypes()[0]);// base.getOrSetClassTypeSubstitutionID( objectToPersist.getClass().getName());
transactionFile.writeShort(translatedObjectClassID);
}
// transactionFile.writeShort(classTypes.length);
//
// for (int i = 0; i < classTypes.length; i++) {
// int id = base.getOrSetClassTypeSubstitutionID(classTypes[i]);
// transactionFile.writeShort(id);
// }
if(fieldsWithAbsoluteAddr.size()>0){//with absolute offsets
transactionFile.writeShort(fieldsWithAbsoluteAddr.size());
for (int i = 0; i < fieldsWithAbsoluteAddr.size(); i++) {//writing links of unchanged objects
ObjectFieldRecord next = fieldsWithAbsoluteAddr.elementAt(i);
int id = base.getOrSetFieldSubstitutionID(next._field);
transactionFile.writeShort(id);
TransactionHandle valueHandle = transactionObjects.get(next._value);
transactionFile.writeLong(valueHandle.getHandle().getObjectEntryOffset());
}
}
long objectsWithRelativeAddrStartOffsetShift = -1;
if(fieldsWithRelativeAddr.size()>0){//with relative offsets
transactionFile.writeShort(fieldsWithRelativeAddr.size());
objectsWithRelativeAddrStartOffsetShift = transactionFile.getCursorOffset() - objectIDOffset;
transactionFile.setLength(transactionFile.length()+ fieldsWithRelativeAddr.size()*(2+4));
transactionFile.seek(transactionFile.length());
}
if(primitiveFields.size()>0){
transactionFile.writeShort(primitiveFields.size());
}
for (int i = 0; i < primitiveFields.size(); i++) {
Field next = primitiveFields.elementAt(i);
int id = base.getOrSetFieldSubstitutionID(next);
transactionFile.writeShort(id);
IndexingRecord record = IndexingRecord.findIndexingRecord(id, indexes);
if(record!=null){
//ByteBuffer currentlyPersistedValue = record.getPersistedDataBuffer();
ByteBuffer pendingValue = record.getPendingDataBuffer();
pendingValue.clear();
PrimitiveJavaTypesUtil.primitiveToByteBuffer(objectToPersist, next, pendingValue);
pendingValue.flip();
transactionFile.getChannel().write(pendingValue);
pendingValue.rewind();
}else {
try {
Utils.writePrimitive(objectToPersist, next,transactionFile);
} catch (Exception e) {
throw new JodbIOException(e);
}
}
}
long arrayDataShift = 0;
if(classDescr.isArray()){
int arrayLength = Array.getLength(objectToPersist);
transactionFile.writeInt(arrayLength);
transactionFile.writeByte(arrayElementSize);//write length of each element in array
arrayDataShift = transactionFile.getCursorOffset() - objectIDOffset;
boolean primitive = classDescr.isPrimitiveArray();
if(primitive){//completely write primitive array
try {
Utils.writePrimitiveArray(objectToPersist, classDescr.getArrayType(), 0, arrayLength, transactionFile);
} catch (Exception e) {
_logger.log(Level.SEVERE,"",e);
throw new JodbIOException(e);
}
}else{//reserve space for references
long spaceToReserve = arrayElementSize*arrayLength;
long slotMasksTotal = arrayLength/8;
if( slotMasksTotal*8 != arrayLength){
slotMasksTotal++;//trailing mask entry for slot <8
}
spaceToReserve+=slotMasksTotal;
if(transactionFile.getCursorOffset() + spaceToReserve > transactionFile.length() ){
transactionFile.setLength(transactionFile.getCursorOffset() + spaceToReserve );
}
transactionFile.skip(spaceToReserve);
}
}
if(JODBConfig.DEBUG){
_logger.info(" <<< Transaction: Object "+rootObject.getClass()+" "+rootObject+" end offset ="+transactionFile.getCursorOffset());
}
long objectEndOffset = transactionFile.getCursorOffset();
long objectBodyLength = objectEndOffset - objectBodyOffset;
if(lengthEstimate < objectBodyLength){
throw new JodbIOException("Object length estimate error");
}
//long targetObjectBodyLength = objectBodyLength;
if(!tHandle.isNewObject()){
DataContainersCache dataContainersCache = TransactionUtils.getObjectDataContainerCache();
ObjectDataContainer existingObjectHeaderData = dataContainersCache.pullObjectDataContainer();// tContainer.getTempObjectDataContainer();
//ioTicket.getRandomAccessBuffer().seek(tHandle.getHandle().getObjectEntryOffset());
//JODBIOUtils.readObjectHeader(ioTicket, existingObjectHeaderData, false);
existingObjectHeaderData.readHeader(ioTicket.getRandomAccessBuffer(),tHandle.getHandle().getObjectEntryOffset(), false);
tHandle.setTransactionOffset(existingObjectHeaderData.getOffset());//JODBIOUtils.addAbsoluteOffsetIdentifierBit(existingObjectHeaderData.getOffset()));//if object already existed than alvays point to initial object position
long redirectorOffset = existingObjectHeaderData.isRedirection()?existingObjectHeaderData.getOffset():-1;
if(objectBodyLength > existingObjectHeaderData.getBodyLength() || fieldsWithRelativeAddr.size() > 0 ){
if( existingObjectHeaderData.isRedirection() ){
//redirection entry space is too small, let see what is under redirection offset
//ioTicket.getRandomAccessBuffer().seek(existingObjectHeaderData.getRedirectionOffset());
long existingObjectRedirectionOffset = existingObjectHeaderData.getRedirectionOffset();
existingObjectHeaderData.reset();
existingObjectHeaderData.readHeader(ioTicket.getRandomAccessBuffer(), existingObjectRedirectionOffset, true);
//JODBIOUtils.readObjectHeader(ioTicket, existingObjectHeaderData, true);
}
}
if(objectBodyLength <= existingObjectHeaderData.getBodyLength() && fieldsWithRelativeAddr.size() == 0){
boolean isRedirection = existingObjectHeaderData.isRedirection();
long redirectionOffset = existingObjectHeaderData.getRedirectionOffset();
//long targetObjectBodyLength = existingObjectHeaderData.getBodyLength();//length for new object's header
//object id(length bits) may change as we fit to maybe bigger space
objId = JODBIOBase.ENTRY_OBJECT_ID | existingObjectHeaderData.getLengthModifierFromID();// composeEntryID( JODBIOBase.ENTRY_OBJECT_ID, targetObjectBodyLength);
if(existingObjectHeaderData.isRedirectedObject()){
objIdWithRedirectionBit = JODBIOBase.addRedirectedObjectModifier(objId);//this is redirected entry
}else{
objIdWithRedirectionBit = objId;
}
if( isRedirection ){//if we fit into redirection record than delete record under redirection offset
deleteObject(ioTicket, session, redirectionOffset, tContainer);//delete/backup record under redirection offset
}
//object can fit to old spot, write it to replacements file insteard of transaction file
IRandomAccessDataBuffer replacementsFile = tContainer.getTransactionReplacementsDataFile();
replacementsFile.resetToEnd();
replacementsFile.writeByte(TRANSACTION_REPLACEMENT_ENTRY_TYPE_STATIC);
replacementsFile.writeLong(existingObjectHeaderData.getOffset());
long replacementLengthEntryOffset = replacementsFile.getCursorOffset();
replacementsFile.writeLong(0);//reserve space for replacement length entry. //TODO skip faster?
long newObjectIDOffset = replacementsFile.getCursorOffset();
replacementsFile.writeShort(objIdWithRedirectionBit);//write new ID to replacements file
writeEntryLenForID(objId,objectBodyLength,replacementsFile);//write length of replacements file, could be bigger than actual object's data occupies
long newObjectBodyOffset = replacementsFile.getCursorOffset();
//return to write actual length of replacement entry
replacementsFile.seek(replacementLengthEntryOffset);
replacementsFile.writeLong(newObjectBodyOffset-newObjectIDOffset+objectBodyLength);
replacementsFile.seek(newObjectBodyOffset);//back to the header end
transactionFile.transferTo(objectBodyOffset, objectBodyLength, replacementsFile.getChannel());
transactionFile.seek(objectIDOffset);//return position in transaction file to the start of object(like it wasn't here)
transactionFile.setLength(objectIDOffset);//truncate "new data" file
objectIDOffset = newObjectIDOffset;//this now offset in replacements file
objectBodyOffset = newObjectBodyOffset;
objectBodyLength = existingObjectHeaderData.getBodyLength();//length for new object
transactionFile = replacementsFile;
replacementsFile.resetToEnd();//replacements file to the end
objectEndOffset = replacementsFile.getCursorOffset();
}else{
if(redirectorOffset!=-1){
//this is record under redirection offset
deleteObject(ioTicket, session, existingObjectHeaderData.getOffset(), tContainer);
}
IRandomAccessDataBuffer replacementsFile = tContainer.getTransactionReplacementsDataFile();
long offset = tHandle.getHandle().getObjectEntryOffset();//offset of record that will be replaced with redirector
//backupObject(ioTicket, offset, tContainer);
//write redirector entry with relative offset
replacementsFile.writeByte(TRANSACTION_REPLACEMENT_ENTRY_TYPE_REDIRECTOR);
replacementsFile.writeLong(offset);
replacementsFile.writeLong(objectIDOffset);//relative offset in new data transaction file
}
dataContainersCache.pushObjectDataContainer(existingObjectHeaderData);
}
for (int i = 0; i < fieldsWithRelativeAddr.size(); i++) {
ObjectFieldRecord next = fieldsWithRelativeAddr.elementAt(i);
next._offset = assembleTransactionDataForObject(context, next._value , tContainer);
// if(JODBIOUtils.isAbsoluteOffset(next._offset)){
// throw new IOException("internal transaction error");
// }
}
if(classDescr.isArray() && !classDescr.isPrimitiveArray()){
int arrayLength = Array.getLength(objectToPersist);
for (int i = 0; i < arrayLength; i++) {
Object value = Array.get(objectToPersist, i);
if (value == null) {
continue;
}
TransactionHandle transactionHandle = transactionObjects.get(value);
if(transactionHandle == null || transactionHandle.isTranslated()){
continue;
}
if(transactionHandle.isNewObject()){//only write new objects as we need relative offset, the offset for existing objects already known
assembleTransactionDataForObject(context, value , tContainer);
}
}
}
transactionFile.seek(objectIDOffset+2);
writeEntryLenForID(objId,objectBodyLength,transactionFile);//TODO make sure the same size estimate is used
if(fieldsWithRelativeAddr.size() > 0){
transactionFile.seek(objectIDOffset+objectsWithRelativeAddrStartOffsetShift);
// if(JODBConfig.DEBUG){
// _logger.info("Transaction: Object "+rootObject.getClass()+" "+rootObject+" fields with relative pos ="+transactionFile.getCursorOffset());
// }
}
for (int i = 0; i < fieldsWithRelativeAddr.size(); i++) {
ObjectFieldRecord next = fieldsWithRelativeAddr.elementAt(i);
int id = base.getOrSetFieldSubstitutionID(next._field);
transactionFile.writeShort(id);
transactionFile.writeInt((int)(next._offset - transactionFile.getCursorOffset()-4));//4 bytes to assume the position after offset entry
}
if(classDescr.isArray() && !classDescr.isPrimitiveArray()){
transactionFile.seek(objectIDOffset+arrayDataShift);
int arrayLength = Array.getLength(objectToPersist);
int slotMask = 0;
int slotCounter = 0;
for (int i = 0; i < arrayLength; i++, slotCounter++) {
if(slotCounter == 8){
transactionFile.writeByte(slotMask);
slotCounter = 0;
slotMask = 0;
}
Object value = Array.get(objectToPersist, i);
TransactionHandle cellValueTransactionHandle;
if (value == null || (cellValueTransactionHandle = transactionObjects.get(value))==null) {
//transaction handle can be null only if this object is last in depth
if (arrayElementSize == 4) {
transactionFile.writeInt(0);
} else {
transactionFile.writeLong(0);
}
continue;
}
if(cellValueTransactionHandle.isNewObject()&!cellValueTransactionHandle.isTranslated()){
throw new IOException();
}
long offset;
boolean absoluteOffset;
if(!cellValueTransactionHandle.isNewObject()){
offset = cellValueTransactionHandle.getHandle().getObjectEntryOffset();
absoluteOffset = true;
if(offset > MAX_ABSOLUTE_SHORT_ADDR && arrayElementSize == 4){
if(tHandle.isNewObject()){
//try relative address
if(context.getTransactionOffset() - offset > TRANSACTION_SIZE_LIMIT){
throw new JodbIOException("Illegal array size estimation TRANSACTION_SIZE_LIMIT");
}
offset = offset - (context.getTransactionOffset()+transactionFile.getCursorOffset()+ arrayElementSize);
absoluteOffset = false;
}else{
throw new JodbIOException("Illegal array size estimation");
}
}
}else{
if(tHandle.isNewObject()){//definitely relative offset
offset = cellValueTransactionHandle.getTransactionOffset() - (transactionFile.getCursorOffset()+arrayElementSize);
absoluteOffset = false;
}else{//only absolute addr from here
offset = cellValueTransactionHandle.getTransactionOffset();
if(arrayElementSize == 4 && offset > MAX_ABSOLUTE_SHORT_ADDR - TRANSACTION_SIZE_LIMIT ){
throw new JodbIOException("Illegal array size estimation > MAX_ABSOLUTE_SHORT_ADDR - TRANSACTION_SIZE_LIMIT");
}
offset+=context.getTransactionOffset();
absoluteOffset = true;
}
}
if (arrayElementSize == 4) {
transactionFile.writeInt((int) offset);
} else {
transactionFile.writeLong(offset);
}
if(absoluteOffset){
slotMask|=1<<slotCounter;//absolute address
}
}
if(slotCounter != 0){//write trailing mask entry
transactionFile.writeByte(slotMask);
slotCounter = 0;
slotMask = 0;
}
}
if(session.getBase().isRemote()){//calc hash identity in Client/Server mode
transactionFile.seek(objectIDOffset);
int hash = Utils.oathash(transactionFile, objectEndOffset - objectIDOffset);
tHandle.setIdentityHash(hash);
}
transactionFile.seek(objectEndOffset);
return tHandle.getTransactionOffset();
}
private static void deleteObject(IOTicket ioTicket, JODBSession session, TransactionHandle transactionHandle, TransactionContainer tContainer) throws IOException{
PersistentObjectHandle handle = transactionHandle.getHandle();
long persistentObjectOffset = handle.getObjectEntryOffset();
transactionHandle.setTransactionOffset(0);
deleteObject(ioTicket, session, persistentObjectOffset, tContainer);
}
private static void deleteObject(IOTicket ioTicket, JODBSession session, long persistentObjectOffset, TransactionContainer tContainer) throws IOException{
//ioTicket.getRandomAccessBuffer().seek(persistentObjectOffset);
IRandomAccessDataBuffer replacementsFile = tContainer.getTransactionReplacementsDataFile();
DataContainersCache dataContainersCache = TransactionUtils.getObjectDataContainerCache();
ObjectDataContainer container = dataContainersCache.pullObjectDataContainer();
//JODBIOUtils.readObjectHeader(ioTicket, container, false);
container.readHeader(ioTicket.getRandomAccessBuffer(),persistentObjectOffset, false);
replacementsFile.writeByte(TRANSACTION_REPLACEMENT_ENTRY_TYPE_STATIC);
replacementsFile.writeLong(persistentObjectOffset);//write offset of object to replace
replacementsFile.writeLong(0);//reserving space for entry's length
long entryOffsetStart = replacementsFile.getCursorOffset();
TransactionUtils.writeEmptyObjectEntry(replacementsFile , container.getBodyLength());
long entryEnd = replacementsFile.getCursorOffset();
long entryLength = entryEnd - entryOffsetStart;
replacementsFile.seek(entryOffsetStart-8);//go back to entry's length
replacementsFile.writeLong(entryLength);
if(container.isRedirection()){
persistentObjectOffset = container.getOffset();
deleteObject(ioTicket, session, persistentObjectOffset, tContainer);
}
dataContainersCache.pushObjectDataContainer(container);
replacementsFile.seek(entryEnd);
}
private static boolean checkActiveObjectUnchanged(IClassProcessor processor, JODBOperationContext context, Object obj, TransactionHandle tHandle, ObjectDataContainer persistentCopyObjectDataContainer, Vector<IndexingRecord> indexes) throws IOException, IllegalClassTypeException{
//TODO add mask verification
if(tHandle.isUnchanged()){
return true;
}
if(tHandle.isNewObject()){
return false;
}
FieldsIterator fieldsIterator = persistentCopyObjectDataContainer.readObject(context, tHandle.getHandle().getObjectEntryOffset(), true, indexes);
tHandle.setCyclicalVersionCounter(persistentCopyObjectDataContainer.getCyclicVersionCounter());
if (fieldsIterator == null) {
return false;
}
return processor.equals(obj, persistentCopyObjectDataContainer, context, null);
}
private static class ByteHolder{
public byte _value;
public ByteHolder() {
}
/**
* @param value
*/
public ByteHolder(byte value) {
super();
_value = value;
}
}
private static class ObjectFieldRecord{
public Field _field;
public Object _value;
public long _offset;
public ObjectFieldRecord(Field field, Object value) {
super();
_field = field;
_value = value;
}
}
}