0001: /**
0002: * Copyright Mobixess Inc. 2007
0003: */package com.mobixess.jodb.core.transaction;
0004:
0005: import java.io.IOException;
0006: import java.lang.reflect.Array;
0007: import java.lang.reflect.Field;
0008: import java.nio.ByteBuffer;
0009: import java.util.Iterator;
0010: import java.util.Map;
0011: import java.util.Random;
0012: import java.util.Vector;
0013: import java.util.logging.Level;
0014: import java.util.logging.Logger;
0015:
0016: import com.mobixess.jodb.core.IllegalClassTypeException;
0017: import com.mobixess.jodb.core.JODBConfig;
0018: import com.mobixess.jodb.core.JodbIOException;
0019: import com.mobixess.jodb.core.agent.JODBAgent;
0020: import com.mobixess.jodb.core.index.IndexingRecord;
0021: import com.mobixess.jodb.core.index.JODBIndexingRootAgent;
0022: import com.mobixess.jodb.core.io.IOTicket;
0023: import com.mobixess.jodb.core.io.IRandomAccessDataBuffer;
0024: import com.mobixess.jodb.core.io.IOBase;
0025: import com.mobixess.jodb.core.io.JODBIOBase;
0026: import com.mobixess.jodb.core.io.JODBOperationContext;
0027: import com.mobixess.jodb.core.io.ObjectDataContainer;
0028: import com.mobixess.jodb.core.io.ObjectDataContainer.FieldsIterator;
0029: import com.mobixess.jodb.core.plugin.IClassProcessor;
0030: import com.mobixess.jodb.core.plugin.JODBPluginRegistry;
0031: import com.mobixess.jodb.core.transaction.JODBSession.ClassDescriptor;
0032: import com.mobixess.jodb.core.transaction.JODBSession.FieldAndIDRecord;
0033: import com.mobixess.jodb.core.transaction.TransactionUtils.DataContainersCache;
0034: import com.mobixess.jodb.util.PrimitiveJavaTypesUtil;
0035: import com.mobixess.jodb.util.Utils;
0036:
0037: /**
0038: * @author Mobixess
0039: *
0040: */
0041: public class TransactionAssembler {
0042:
0043: public final static byte TRANSACTION_REPLACEMENT_ENTRY_TYPE_STATIC = 0xf;
0044: public final static byte TRANSACTION_REPLACEMENT_ENTRY_TYPE_REDIRECTOR = 0x7f;
0045: private static Logger _logger = Utils
0046: .getLogger(TransactionAssembler.class.getName());
0047: private final static long MAX_ABSOLUTE_SHORT_ADDR = 0xFFFFFFFFL;
0048: //WARNING: this is hard transaction limit.
0049: private final static long TRANSACTION_SIZE_LIMIT = Math.min(
0050: Integer.MAX_VALUE, -Integer.MIN_VALUE) / 2;
0051:
0052: /**
0053: *
0054: * @param context
0055: * @param tContainer
0056: * @throws IOException
0057: * @throws IllegalClassTypeException
0058: */
0059: public static void assembleTransactionData(
0060: JODBOperationContext context,
0061: TransactionContainer tContainer) throws IOException,
0062: IllegalClassTypeException {
0063: if (tContainer.isAgentsMode()) {
0064: //make sure the root agent processed first
0065: JODBIndexingRootAgent indexingRootAgent = context
0066: .getIndexingRootAgent();
0067: if (tContainer.getHandleForObject(indexingRootAgent) != null) {
0068: assembleTransactionDataForObject(context,
0069: indexingRootAgent, tContainer);
0070: }
0071: }
0072: Map<Object, TransactionHandle> transactionObjects = tContainer
0073: .getTransactionObjects();
0074: Iterator<Object> iter = transactionObjects.keySet().iterator();
0075: while (iter.hasNext()) {
0076: tContainer.resetTransactionBufferToEnd();
0077: Object element = iter.next();
0078: assembleTransactionDataForObject(context, element,
0079: tContainer);
0080: }
0081: }
0082:
0083: private static long estimateHeaderAndAuxDataLength(
0084: TransactionHandle tHandle, /*int classHierarhyLen,*/
0085: boolean translated) {
0086: long lengthEstimate = 0;
0087: lengthEstimate += 2; // mask
0088: if (tHandle.generateUID()) {
0089: lengthEstimate += 8; // uid
0090: }
0091: if (tHandle.generateCreationTS()) {
0092: lengthEstimate += 8; // creation TS
0093: }
0094: if (tHandle.generateModificationTS()) {
0095: lengthEstimate += 8; // modification TS
0096: }
0097:
0098: lengthEstimate += 2;// class hierarhy
0099: if (translated) {
0100: lengthEstimate += 2;
0101: }
0102: // lengthEstimate += 2; //class names counter
0103: // lengthEstimate += 2 * classHierarhyLen;// class hierarhy
0104: return lengthEstimate;
0105: }
0106:
0107: private static long estimateObjectLength(TransactionHandle tHandle, /*int classHierarhyLen,*/
0108: int fieldsWithAbsoluteLen, int fieldsWithRelativeLen,
0109: ClassDescriptor classDescriptor, boolean translated)
0110: throws JodbIOException {
0111: long lengthEstimate = estimateHeaderAndAuxDataLength(tHandle,
0112: translated);
0113:
0114: if (fieldsWithAbsoluteLen > 0) {
0115: lengthEstimate += 2; // directly addressed fields count
0116: lengthEstimate += (2 + 8) * fieldsWithAbsoluteLen;// size for fields ids and pointers
0117: }
0118:
0119: if (fieldsWithRelativeLen > 0) {
0120: lengthEstimate += 2; // inderectly addressed fields count
0121: lengthEstimate += (2 + 4) * fieldsWithRelativeLen;// size for fields ids and pointers
0122: }
0123:
0124: if (classDescriptor != null) {
0125: lengthEstimate += 2; // inderectly addressed fields count
0126: lengthEstimate += classDescriptor
0127: .getPrimitiveFieldsStorageEstimate(2);
0128: }
0129: return lengthEstimate;
0130: }
0131:
0132: private static long estimateArrayObjectLength(
0133: JODBOperationContext context, TransactionHandle tHandle,
0134: Object array, ClassDescriptor classDescriptor,
0135: ByteHolder elementsSizeOutParam, boolean translated)
0136: throws JodbIOException {
0137: int arraySize = Array.getLength(array);
0138: long lengthEstimate = estimateObjectLength(tHandle, 0, 0, null,
0139: translated);//header
0140: lengthEstimate += 4; //array length entry
0141: lengthEstimate += 1; //array's element size entry
0142: if (classDescriptor.isPrimitiveArray()) {
0143: elementsSizeOutParam._value = (byte) PrimitiveJavaTypesUtil
0144: .getDataOutputWriteLen(classDescriptor
0145: .getArrayType());
0146: lengthEstimate += elementsSizeOutParam._value * arraySize;
0147: } else {
0148: JODBSession session = context.getSession();
0149: TransactionContainer tContainer = context
0150: .getTransactionContainer();
0151: long transactionOffset = context.getTransactionOffset();
0152: boolean newArray = tHandle.isNewObject();
0153: elementsSizeOutParam._value = 4;
0154: for (int i = 0; i < arraySize; i++) {
0155: Object value = Array.get(array, i);
0156: if (value == null) {
0157: continue;
0158: }
0159:
0160: TransactionHandle valueTransactionHandle = tContainer
0161: .getHandleForObject(value);
0162: if (valueTransactionHandle == null) {
0163: continue;
0164: }
0165:
0166: if (newArray) {
0167: if (!valueTransactionHandle.isNewObject()) {
0168: long valueObjectOffset = valueTransactionHandle
0169: .getHandle().getObjectEntryOffset();
0170: if (valueObjectOffset > MAX_ABSOLUTE_SHORT_ADDR
0171: && transactionOffset
0172: - valueObjectOffset > TRANSACTION_SIZE_LIMIT) {
0173: elementsSizeOutParam._value = 8;
0174: break;
0175: }
0176: }
0177: } else {
0178: if (valueTransactionHandle.isNewObject()) {
0179: if (transactionOffset > MAX_ABSOLUTE_SHORT_ADDR
0180: - TRANSACTION_SIZE_LIMIT) {
0181: elementsSizeOutParam._value = 8;
0182: break;
0183: }
0184: } else {
0185: long valueObjectOffset = valueTransactionHandle
0186: .getHandle().getObjectEntryOffset();
0187: if (valueObjectOffset > MAX_ABSOLUTE_SHORT_ADDR) {
0188: elementsSizeOutParam._value = 8;
0189: break;
0190: }
0191: }
0192: }
0193:
0194: PersistentObjectHandle objectHandle = session
0195: .getHandleForActiveObject(value);
0196: if (newArray) {
0197:
0198: }
0199: if (objectHandle == null) {
0200: continue;
0201: }
0202: if (objectHandle.getObjectEntryOffset() > MAX_ABSOLUTE_SHORT_ADDR) {
0203: elementsSizeOutParam._value = 8;
0204: break;
0205: }
0206: }
0207: lengthEstimate += elementsSizeOutParam._value * arraySize
0208: + arraySize / 8 + 1;
0209: }
0210: return lengthEstimate;
0211: }
0212:
0213: private static int composeEntryID(short entryID, long length) {
0214: int lenModifierID = 0;
0215: if (length <= 0xff) {
0216: lenModifierID = JODBIOBase.LEN_MODIFIER_BYTE;
0217: } else if (length > 0xFFFF) {
0218: lenModifierID = JODBIOBase.LEN_MODIFIER_LONG;
0219: }
0220: return entryID | lenModifierID;
0221: }
0222:
0223: private static void classifyFields(Object object,
0224: ClassDescriptor classDescr,
0225: Map<Object, TransactionHandle> transactionObjects,
0226: Vector<ObjectFieldRecord> fieldsWithAbsoluteAddr,
0227: Vector<ObjectFieldRecord> fieldsWithRelativeAddr,
0228: Vector<Field> primitiveFields) throws IOException {
0229: FieldAndIDRecord[] fields = classDescr.getFields();
0230: for (int i = 0; i < fields.length; i++) {
0231: Field next = fields[i]._field;
0232: if (next.getType().isPrimitive()) {
0233: primitiveFields.add(next);
0234: continue;
0235: }
0236: Object value;
0237: try {
0238: value = next.get(object);
0239: } catch (Exception e) {
0240: e.printStackTrace();
0241: throw new JodbIOException(e);
0242: }
0243: TransactionHandle childObjectHandle = transactionObjects
0244: .get(value);
0245: if (value == null || childObjectHandle == null
0246: || childObjectHandle.is_DELETE_Transaction()) {
0247: //null fieds may not require
0248: //nullFields.add(next);
0249: } else {
0250: if (!childObjectHandle.isNewObject()) {
0251: fieldsWithAbsoluteAddr.add(new ObjectFieldRecord(
0252: next, value));
0253: } else {
0254: fieldsWithRelativeAddr.add(new ObjectFieldRecord(
0255: next, value));
0256: }
0257: }
0258: }
0259: }
0260:
0261: private static void writeEntryLenForID(int id, long length,
0262: IRandomAccessDataBuffer dataBuffer) throws IOException {
0263: switch (id & ~JODBIOBase.LEN_MODIFIER_EXCLUSION_MASK) {//reserve space for length
0264: case JODBIOBase.LEN_MODIFIER_BYTE:
0265: dataBuffer.writeByte((byte) length);
0266: break;
0267: case JODBIOBase.LEN_MODIFIER_LONG:
0268: dataBuffer.writeLong(length);
0269: break;
0270: default:
0271: dataBuffer.writeShort((short) length);
0272: }
0273: }
0274:
0275: private static int formPrimaryObjectMask(int initalMask,
0276: boolean fieldsWithAbsoluteAddr,
0277: boolean fieldsWithRelativeAddr, boolean primitiveFields,
0278: boolean translated, TransactionHandle tHandle,
0279: ClassDescriptor classDescr) {
0280: if (fieldsWithAbsoluteAddr) {
0281: initalMask = ObjectDataContainer
0282: .addDirectlyAddressedFieldsBit(initalMask);
0283: }
0284: if (fieldsWithRelativeAddr) {
0285: initalMask = ObjectDataContainer
0286: .addRelativelyAddressedFieldsID(initalMask);
0287: }
0288: if (primitiveFields) {
0289: initalMask = ObjectDataContainer
0290: .addPrimitiveFieldsBit(initalMask);
0291: }
0292:
0293: if (translated) {
0294: initalMask = ObjectDataContainer
0295: .addTranslatedBit(initalMask);
0296: }
0297:
0298: if (tHandle.generateCreationTS()) {
0299: initalMask = ObjectDataContainer
0300: .addCreationTSFieldBit(initalMask);
0301: }
0302:
0303: if (tHandle.generateModificationTS()) {
0304: initalMask = ObjectDataContainer
0305: .addModificationTSFieldBit(initalMask);
0306: }
0307:
0308: if (classDescr.isArray()) {
0309: initalMask = ObjectDataContainer.addArrayIDBit(initalMask);
0310: }
0311: return initalMask;
0312: }
0313:
0314: private static int formSecondaryObjectMask(int initalMask,
0315: boolean isAgentObject) {
0316: if (isAgentObject) {
0317: initalMask = ObjectDataContainer.addAgentBit(initalMask);
0318: }
0319: return initalMask;
0320: }
0321:
0322: private static long assembleTransactionDataForObject(
0323: JODBOperationContext context, Object rootObject,
0324: TransactionContainer tContainer) throws IOException,
0325: IllegalClassTypeException {
0326: DataContainersCache dataContainersCache = TransactionUtils
0327: .getObjectDataContainerCache();
0328: ObjectDataContainer persistentCopyObjectDataContainer = dataContainersCache
0329: .pullObjectDataContainer();
0330: try {
0331: return writeObjects(context, rootObject, tContainer,
0332: persistentCopyObjectDataContainer);
0333: } finally {
0334: dataContainersCache
0335: .pushObjectDataContainer(persistentCopyObjectDataContainer);
0336: tContainer.fireOnCommitFinished(rootObject, context
0337: .getSession());
0338: }
0339: }
0340:
0341: /**
0342: *
0343: * @param context
0344: * @param rootObject
0345: * @param tContainer
0346: * @param persistentCopyObjectDataContainer - data container to read already persisted copy if applicable
0347: * @return
0348: * @throws IOException
0349: * @throws IllegalClassTypeException
0350: */
0351: private static long writeObjects(JODBOperationContext context,
0352: Object rootObject, TransactionContainer tContainer,
0353: ObjectDataContainer persistentCopyObjectDataContainer)
0354: throws IOException, IllegalClassTypeException {
0355: //TODO add verification "fields count" < short
0356: Map<Object, TransactionHandle> transactionObjects = tContainer
0357: .getTransactionObjects();
0358: TransactionHandle tHandle = transactionObjects.get(rootObject);
0359: if (tHandle == null) {
0360: throw new IOException("transaction handle unavailable");
0361: }
0362: if (!tContainer.isAgentsMode() && tHandle.isAgent()) {
0363: return 0;
0364: }
0365: IOTicket ioTicket = context.getIoTicket();
0366: JODBSession session = context.getSession();
0367: if (tHandle.isTranslated()) {
0368: return tHandle.getTransactionOffset();
0369: }
0370: tContainer
0371: .fireOnCommitStarted(rootObject, context.getSession());
0372: if (tHandle.is_DELETE_Transaction()) {
0373: deleteObject(ioTicket, session, tHandle, tContainer);
0374: return -1;
0375: }
0376: IOBase base = ioTicket.getBase();
0377:
0378: IClassProcessor classProcessor = JODBPluginRegistry
0379: .getInstance().getClassProcessor(rootObject.getClass());
0380:
0381: Object objectToPersist = classProcessor.translate(rootObject);
0382:
0383: ClassDescriptor classDescr = session
0384: .getDescriptorForClass(objectToPersist.getClass());
0385: FieldAndIDRecord[] fields = classDescr.getFields();
0386:
0387: Vector<IndexingRecord> indexes = null;
0388:
0389: Class rootObjectType = rootObject.getClass();
0390: int rootObjectClassID;
0391: if (rootObjectType.isArray()) {
0392: rootObjectClassID = base
0393: .getOrSetClassTypeSubstitutionID(rootObjectType
0394: .getComponentType().getName());
0395: } else {
0396: rootObjectClassID = base
0397: .getOrSetClassTypeSubstitutionID(rootObjectType
0398: .getName());
0399: indexes = TransactionUtils.getObjectDataContainerCache()
0400: .pullVector();
0401: JODBIndexingRootAgent indexingAgent = context
0402: .getIndexingRootAgent();
0403: indexingAgent.getAgentsForClassId(indexes,
0404: rootObjectClassID);
0405: if (indexes.size() == 0) {
0406: //no indexes for this class
0407: TransactionUtils.getObjectDataContainerCache()
0408: .pushVector(indexes);
0409: indexes = null;
0410: }
0411: }
0412: tHandle.setIndexes(indexes);
0413: //String[] classTypes = classDescr.getTypes();
0414:
0415: if (checkActiveObjectUnchanged(classProcessor, context,
0416: objectToPersist, tHandle,
0417: persistentCopyObjectDataContainer, indexes)) {//should not happen in recursive sub call
0418: tHandle.setIndexes(null);//reset indexes info in handle to prevent post processing
0419: long offset = tHandle.getHandle().getObjectEntryOffset();//JODBIOUtils.addAbsoluteOffsetIdentifierBit(tHandle.getHandle().getObjectEntryOffset());
0420: tHandle.setTransactionOffset(offset);
0421: if (classDescr.isArray()) {
0422: if (classDescr.isPrimitiveArray()) {
0423: return offset;
0424: }
0425: int arrayLen = Array.getLength(objectToPersist);
0426: for (int i = 0; i < arrayLen; i++) {
0427: Object childObj = Array.get(objectToPersist, i);
0428: if (childObj == null
0429: || transactionObjects.get(childObj) == null) {
0430: continue;
0431: }
0432: assembleTransactionDataForObject(context, childObj,
0433: tContainer);
0434: }
0435: } else {
0436: try {
0437: for (int i = 0; i < fields.length; i++) {
0438: Field field = fields[i]._field;
0439: if (field.getType().isPrimitive()) {
0440: continue;
0441: }
0442: Object childObj = field.get(objectToPersist);
0443: if (childObj == null
0444: || transactionObjects.get(childObj) == null) {
0445: continue;
0446: }
0447: assembleTransactionDataForObject(context,
0448: childObj, tContainer);
0449: }
0450: } catch (Exception e) {
0451: e.printStackTrace();
0452: throw new JodbIOException(e);
0453: }
0454: }
0455: return offset;
0456: }
0457:
0458: IRandomAccessDataBuffer transactionFile = tContainer
0459: .getTransactionNewDataFile();
0460: transactionFile.resetToEnd();
0461:
0462: Vector<ObjectFieldRecord> fieldsWithAbsoluteAddr = new Vector<ObjectFieldRecord>();
0463: Vector<ObjectFieldRecord> fieldsWithRelativeAddr = new Vector<ObjectFieldRecord>();
0464: Vector<Field> primitiveFields = new Vector<Field>();
0465:
0466: if (!classDescr.isArray()) {
0467: classifyFields(objectToPersist, classDescr,
0468: transactionObjects, fieldsWithAbsoluteAddr,
0469: fieldsWithRelativeAddr, primitiveFields);
0470: }
0471:
0472: long objectIDOffset = transactionFile.getCursorOffset();
0473:
0474: tHandle.setTransactionOffset(objectIDOffset);
0475:
0476: if (JODBConfig.DEBUG) {
0477: _logger.info(" >>> Transaction: Object "
0478: + rootObject.getClass() + " " + rootObject
0479: + " start offset =" + objectIDOffset);
0480: }
0481:
0482: boolean translated = rootObject != objectToPersist;
0483: byte arrayElementSize = 0;
0484: long lengthEstimate;
0485: if (!classDescr.isArray()) {
0486: lengthEstimate = estimateObjectLength(tHandle,
0487: fieldsWithAbsoluteAddr.size(),
0488: fieldsWithRelativeAddr.size(), classDescr,
0489: translated);
0490: } else {
0491: ByteHolder byteHolder = new ByteHolder();
0492: lengthEstimate = estimateArrayObjectLength(context,
0493: tHandle, objectToPersist, classDescr, byteHolder,
0494: translated);
0495: arrayElementSize = byteHolder._value;
0496: }
0497:
0498: int objId = composeEntryID(JODBIOBase.ENTRY_OBJECT_ID,
0499: lengthEstimate);
0500: int objIdWithRedirectionBit = tHandle.isNewObject() ? objId
0501: : JODBIOBase.addRedirectedObjectModifier(objId);
0502: transactionFile.writeShort(objIdWithRedirectionBit);
0503: writeEntryLenForID(objId, 0, transactionFile);//reserve space for length
0504:
0505: long objectBodyOffset = transactionFile.getCursorOffset();
0506:
0507: // if(JODBConfig.DEBUG){
0508: // _logger.info("Transaction: Object "+rootObject.getClass()+" "+rootObject+" header len ="+headerLen);
0509: // }
0510:
0511: int primaryMask = formPrimaryObjectMask(0,
0512: fieldsWithAbsoluteAddr.size() > 0,
0513: fieldsWithRelativeAddr.size() > 0, primitiveFields
0514: .size() > 0, translated, tHandle, classDescr);
0515: ;
0516:
0517: transactionFile.writeByte(primaryMask);
0518:
0519: int secondaryMask = formSecondaryObjectMask(0, tHandle
0520: .isAgent());
0521:
0522: transactionFile.writeByte(secondaryMask);
0523:
0524: tHandle.setTranslatedObjectDataMask((byte) primaryMask);
0525:
0526: //
0527: if (tHandle.generateUID()) {
0528: Random random = new Random();
0529: transactionFile.writeLong(random.nextLong());
0530: }
0531: long time = System.currentTimeMillis();
0532: if (tHandle.generateCreationTS()) {
0533: transactionFile.writeLong(time);
0534: }
0535: if (tHandle.generateModificationTS()) {
0536: transactionFile.writeLong(time);
0537: }
0538:
0539: //
0540: transactionFile.writeShort(rootObjectClassID);
0541:
0542: if (translated) {
0543: int translatedObjectClassID = base
0544: .getOrSetClassTypeSubstitutionID(classDescr
0545: .getTypes()[0]);// base.getOrSetClassTypeSubstitutionID( objectToPersist.getClass().getName());
0546: transactionFile.writeShort(translatedObjectClassID);
0547: }
0548:
0549: // transactionFile.writeShort(classTypes.length);
0550: //
0551: // for (int i = 0; i < classTypes.length; i++) {
0552: // int id = base.getOrSetClassTypeSubstitutionID(classTypes[i]);
0553: // transactionFile.writeShort(id);
0554: // }
0555:
0556: if (fieldsWithAbsoluteAddr.size() > 0) {//with absolute offsets
0557: transactionFile.writeShort(fieldsWithAbsoluteAddr.size());
0558: for (int i = 0; i < fieldsWithAbsoluteAddr.size(); i++) {//writing links of unchanged objects
0559: ObjectFieldRecord next = fieldsWithAbsoluteAddr
0560: .elementAt(i);
0561: int id = base.getOrSetFieldSubstitutionID(next._field);
0562: transactionFile.writeShort(id);
0563: TransactionHandle valueHandle = transactionObjects
0564: .get(next._value);
0565: transactionFile.writeLong(valueHandle.getHandle()
0566: .getObjectEntryOffset());
0567: }
0568: }
0569:
0570: long objectsWithRelativeAddrStartOffsetShift = -1;
0571: if (fieldsWithRelativeAddr.size() > 0) {//with relative offsets
0572: transactionFile.writeShort(fieldsWithRelativeAddr.size());
0573: objectsWithRelativeAddrStartOffsetShift = transactionFile
0574: .getCursorOffset()
0575: - objectIDOffset;
0576: transactionFile.setLength(transactionFile.length()
0577: + fieldsWithRelativeAddr.size() * (2 + 4));
0578: transactionFile.seek(transactionFile.length());
0579: }
0580:
0581: if (primitiveFields.size() > 0) {
0582: transactionFile.writeShort(primitiveFields.size());
0583: }
0584: for (int i = 0; i < primitiveFields.size(); i++) {
0585: Field next = primitiveFields.elementAt(i);
0586: int id = base.getOrSetFieldSubstitutionID(next);
0587: transactionFile.writeShort(id);
0588: IndexingRecord record = IndexingRecord.findIndexingRecord(
0589: id, indexes);
0590: if (record != null) {
0591: //ByteBuffer currentlyPersistedValue = record.getPersistedDataBuffer();
0592: ByteBuffer pendingValue = record.getPendingDataBuffer();
0593: pendingValue.clear();
0594: PrimitiveJavaTypesUtil.primitiveToByteBuffer(
0595: objectToPersist, next, pendingValue);
0596: pendingValue.flip();
0597: transactionFile.getChannel().write(pendingValue);
0598: pendingValue.rewind();
0599: } else {
0600: try {
0601: Utils.writePrimitive(objectToPersist, next,
0602: transactionFile);
0603: } catch (Exception e) {
0604: throw new JodbIOException(e);
0605: }
0606: }
0607: }
0608:
0609: long arrayDataShift = 0;
0610: if (classDescr.isArray()) {
0611: int arrayLength = Array.getLength(objectToPersist);
0612: transactionFile.writeInt(arrayLength);
0613: transactionFile.writeByte(arrayElementSize);//write length of each element in array
0614: arrayDataShift = transactionFile.getCursorOffset()
0615: - objectIDOffset;
0616: boolean primitive = classDescr.isPrimitiveArray();
0617: if (primitive) {//completely write primitive array
0618: try {
0619: Utils.writePrimitiveArray(objectToPersist,
0620: classDescr.getArrayType(), 0, arrayLength,
0621: transactionFile);
0622: } catch (Exception e) {
0623: _logger.log(Level.SEVERE, "", e);
0624: throw new JodbIOException(e);
0625: }
0626: } else {//reserve space for references
0627: long spaceToReserve = arrayElementSize * arrayLength;
0628: long slotMasksTotal = arrayLength / 8;
0629: if (slotMasksTotal * 8 != arrayLength) {
0630: slotMasksTotal++;//trailing mask entry for slot <8
0631: }
0632: spaceToReserve += slotMasksTotal;
0633: if (transactionFile.getCursorOffset() + spaceToReserve > transactionFile
0634: .length()) {
0635: transactionFile.setLength(transactionFile
0636: .getCursorOffset()
0637: + spaceToReserve);
0638: }
0639: transactionFile.skip(spaceToReserve);
0640:
0641: }
0642: }
0643:
0644: if (JODBConfig.DEBUG) {
0645: _logger.info(" <<< Transaction: Object "
0646: + rootObject.getClass() + " " + rootObject
0647: + " end offset ="
0648: + transactionFile.getCursorOffset());
0649: }
0650:
0651: long objectEndOffset = transactionFile.getCursorOffset();
0652: long objectBodyLength = objectEndOffset - objectBodyOffset;
0653: if (lengthEstimate < objectBodyLength) {
0654: throw new JodbIOException("Object length estimate error");
0655: }
0656: //long targetObjectBodyLength = objectBodyLength;
0657:
0658: if (!tHandle.isNewObject()) {
0659: DataContainersCache dataContainersCache = TransactionUtils
0660: .getObjectDataContainerCache();
0661: ObjectDataContainer existingObjectHeaderData = dataContainersCache
0662: .pullObjectDataContainer();// tContainer.getTempObjectDataContainer();
0663:
0664: //ioTicket.getRandomAccessBuffer().seek(tHandle.getHandle().getObjectEntryOffset());
0665: //JODBIOUtils.readObjectHeader(ioTicket, existingObjectHeaderData, false);
0666: existingObjectHeaderData.readHeader(ioTicket
0667: .getRandomAccessBuffer(), tHandle.getHandle()
0668: .getObjectEntryOffset(), false);
0669: tHandle.setTransactionOffset(existingObjectHeaderData
0670: .getOffset());//JODBIOUtils.addAbsoluteOffsetIdentifierBit(existingObjectHeaderData.getOffset()));//if object already existed than alvays point to initial object position
0671: long redirectorOffset = existingObjectHeaderData
0672: .isRedirection() ? existingObjectHeaderData
0673: .getOffset() : -1;
0674: if (objectBodyLength > existingObjectHeaderData
0675: .getBodyLength()
0676: || fieldsWithRelativeAddr.size() > 0) {
0677: if (existingObjectHeaderData.isRedirection()) {
0678: //redirection entry space is too small, let see what is under redirection offset
0679: //ioTicket.getRandomAccessBuffer().seek(existingObjectHeaderData.getRedirectionOffset());
0680: long existingObjectRedirectionOffset = existingObjectHeaderData
0681: .getRedirectionOffset();
0682: existingObjectHeaderData.reset();
0683: existingObjectHeaderData.readHeader(ioTicket
0684: .getRandomAccessBuffer(),
0685: existingObjectRedirectionOffset, true);
0686: //JODBIOUtils.readObjectHeader(ioTicket, existingObjectHeaderData, true);
0687: }
0688: }
0689:
0690: if (objectBodyLength <= existingObjectHeaderData
0691: .getBodyLength()
0692: && fieldsWithRelativeAddr.size() == 0) {
0693: boolean isRedirection = existingObjectHeaderData
0694: .isRedirection();
0695: long redirectionOffset = existingObjectHeaderData
0696: .getRedirectionOffset();
0697: //long targetObjectBodyLength = existingObjectHeaderData.getBodyLength();//length for new object's header
0698:
0699: //object id(length bits) may change as we fit to maybe bigger space
0700: objId = JODBIOBase.ENTRY_OBJECT_ID
0701: | existingObjectHeaderData
0702: .getLengthModifierFromID();// composeEntryID( JODBIOBase.ENTRY_OBJECT_ID, targetObjectBodyLength);
0703:
0704: if (existingObjectHeaderData.isRedirectedObject()) {
0705: objIdWithRedirectionBit = JODBIOBase
0706: .addRedirectedObjectModifier(objId);//this is redirected entry
0707: } else {
0708: objIdWithRedirectionBit = objId;
0709: }
0710:
0711: if (isRedirection) {//if we fit into redirection record than delete record under redirection offset
0712: deleteObject(ioTicket, session, redirectionOffset,
0713: tContainer);//delete/backup record under redirection offset
0714: }
0715:
0716: //object can fit to old spot, write it to replacements file insteard of transaction file
0717: IRandomAccessDataBuffer replacementsFile = tContainer
0718: .getTransactionReplacementsDataFile();
0719: replacementsFile.resetToEnd();
0720: replacementsFile
0721: .writeByte(TRANSACTION_REPLACEMENT_ENTRY_TYPE_STATIC);
0722: replacementsFile.writeLong(existingObjectHeaderData
0723: .getOffset());
0724: long replacementLengthEntryOffset = replacementsFile
0725: .getCursorOffset();
0726: replacementsFile.writeLong(0);//reserve space for replacement length entry. //TODO skip faster?
0727: long newObjectIDOffset = replacementsFile
0728: .getCursorOffset();
0729:
0730: replacementsFile.writeShort(objIdWithRedirectionBit);//write new ID to replacements file
0731: writeEntryLenForID(objId, objectBodyLength,
0732: replacementsFile);//write length of replacements file, could be bigger than actual object's data occupies
0733:
0734: long newObjectBodyOffset = replacementsFile
0735: .getCursorOffset();
0736: //return to write actual length of replacement entry
0737: replacementsFile.seek(replacementLengthEntryOffset);
0738: replacementsFile.writeLong(newObjectBodyOffset
0739: - newObjectIDOffset + objectBodyLength);
0740: replacementsFile.seek(newObjectBodyOffset);//back to the header end
0741:
0742: transactionFile
0743: .transferTo(objectBodyOffset, objectBodyLength,
0744: replacementsFile.getChannel());
0745: transactionFile.seek(objectIDOffset);//return position in transaction file to the start of object(like it wasn't here)
0746: transactionFile.setLength(objectIDOffset);//truncate "new data" file
0747: objectIDOffset = newObjectIDOffset;//this now offset in replacements file
0748: objectBodyOffset = newObjectBodyOffset;
0749: objectBodyLength = existingObjectHeaderData
0750: .getBodyLength();//length for new object
0751: transactionFile = replacementsFile;
0752: replacementsFile.resetToEnd();//replacements file to the end
0753: objectEndOffset = replacementsFile.getCursorOffset();
0754: } else {
0755: if (redirectorOffset != -1) {
0756: //this is record under redirection offset
0757: deleteObject(ioTicket, session,
0758: existingObjectHeaderData.getOffset(),
0759: tContainer);
0760: }
0761: IRandomAccessDataBuffer replacementsFile = tContainer
0762: .getTransactionReplacementsDataFile();
0763: long offset = tHandle.getHandle()
0764: .getObjectEntryOffset();//offset of record that will be replaced with redirector
0765: //backupObject(ioTicket, offset, tContainer);
0766: //write redirector entry with relative offset
0767: replacementsFile
0768: .writeByte(TRANSACTION_REPLACEMENT_ENTRY_TYPE_REDIRECTOR);
0769: replacementsFile.writeLong(offset);
0770: replacementsFile.writeLong(objectIDOffset);//relative offset in new data transaction file
0771: }
0772: dataContainersCache
0773: .pushObjectDataContainer(existingObjectHeaderData);
0774: }
0775:
0776: for (int i = 0; i < fieldsWithRelativeAddr.size(); i++) {
0777: ObjectFieldRecord next = fieldsWithRelativeAddr
0778: .elementAt(i);
0779: next._offset = assembleTransactionDataForObject(context,
0780: next._value, tContainer);
0781: // if(JODBIOUtils.isAbsoluteOffset(next._offset)){
0782: // throw new IOException("internal transaction error");
0783: // }
0784: }
0785:
0786: if (classDescr.isArray() && !classDescr.isPrimitiveArray()) {
0787: int arrayLength = Array.getLength(objectToPersist);
0788: for (int i = 0; i < arrayLength; i++) {
0789: Object value = Array.get(objectToPersist, i);
0790: if (value == null) {
0791: continue;
0792: }
0793: TransactionHandle transactionHandle = transactionObjects
0794: .get(value);
0795: if (transactionHandle == null
0796: || transactionHandle.isTranslated()) {
0797: continue;
0798: }
0799: if (transactionHandle.isNewObject()) {//only write new objects as we need relative offset, the offset for existing objects already known
0800: assembleTransactionDataForObject(context, value,
0801: tContainer);
0802: }
0803: }
0804: }
0805:
0806: transactionFile.seek(objectIDOffset + 2);
0807:
0808: writeEntryLenForID(objId, objectBodyLength, transactionFile);//TODO make sure the same size estimate is used
0809:
0810: if (fieldsWithRelativeAddr.size() > 0) {
0811: transactionFile.seek(objectIDOffset
0812: + objectsWithRelativeAddrStartOffsetShift);
0813: // if(JODBConfig.DEBUG){
0814: // _logger.info("Transaction: Object "+rootObject.getClass()+" "+rootObject+" fields with relative pos ="+transactionFile.getCursorOffset());
0815: // }
0816: }
0817: for (int i = 0; i < fieldsWithRelativeAddr.size(); i++) {
0818: ObjectFieldRecord next = fieldsWithRelativeAddr
0819: .elementAt(i);
0820: int id = base.getOrSetFieldSubstitutionID(next._field);
0821: transactionFile.writeShort(id);
0822: transactionFile.writeInt((int) (next._offset
0823: - transactionFile.getCursorOffset() - 4));//4 bytes to assume the position after offset entry
0824: }
0825:
0826: if (classDescr.isArray() && !classDescr.isPrimitiveArray()) {
0827: transactionFile.seek(objectIDOffset + arrayDataShift);
0828: int arrayLength = Array.getLength(objectToPersist);
0829: int slotMask = 0;
0830: int slotCounter = 0;
0831: for (int i = 0; i < arrayLength; i++, slotCounter++) {
0832: if (slotCounter == 8) {
0833: transactionFile.writeByte(slotMask);
0834: slotCounter = 0;
0835: slotMask = 0;
0836: }
0837: Object value = Array.get(objectToPersist, i);
0838: TransactionHandle cellValueTransactionHandle;
0839: if (value == null
0840: || (cellValueTransactionHandle = transactionObjects
0841: .get(value)) == null) {
0842: //transaction handle can be null only if this object is last in depth
0843: if (arrayElementSize == 4) {
0844: transactionFile.writeInt(0);
0845: } else {
0846: transactionFile.writeLong(0);
0847: }
0848: continue;
0849: }
0850: if (cellValueTransactionHandle.isNewObject()
0851: & !cellValueTransactionHandle.isTranslated()) {
0852: throw new IOException();
0853: }
0854: long offset;
0855: boolean absoluteOffset;
0856: if (!cellValueTransactionHandle.isNewObject()) {
0857: offset = cellValueTransactionHandle.getHandle()
0858: .getObjectEntryOffset();
0859: absoluteOffset = true;
0860: if (offset > MAX_ABSOLUTE_SHORT_ADDR
0861: && arrayElementSize == 4) {
0862: if (tHandle.isNewObject()) {
0863: //try relative address
0864: if (context.getTransactionOffset() - offset > TRANSACTION_SIZE_LIMIT) {
0865: throw new JodbIOException(
0866: "Illegal array size estimation TRANSACTION_SIZE_LIMIT");
0867: }
0868: offset = offset
0869: - (context.getTransactionOffset()
0870: + transactionFile
0871: .getCursorOffset() + arrayElementSize);
0872: absoluteOffset = false;
0873: } else {
0874: throw new JodbIOException(
0875: "Illegal array size estimation");
0876: }
0877: }
0878: } else {
0879: if (tHandle.isNewObject()) {//definitely relative offset
0880: offset = cellValueTransactionHandle
0881: .getTransactionOffset()
0882: - (transactionFile.getCursorOffset() + arrayElementSize);
0883: absoluteOffset = false;
0884: } else {//only absolute addr from here
0885: offset = cellValueTransactionHandle
0886: .getTransactionOffset();
0887: if (arrayElementSize == 4
0888: && offset > MAX_ABSOLUTE_SHORT_ADDR
0889: - TRANSACTION_SIZE_LIMIT) {
0890: throw new JodbIOException(
0891: "Illegal array size estimation > MAX_ABSOLUTE_SHORT_ADDR - TRANSACTION_SIZE_LIMIT");
0892: }
0893: offset += context.getTransactionOffset();
0894: absoluteOffset = true;
0895: }
0896:
0897: }
0898: if (arrayElementSize == 4) {
0899: transactionFile.writeInt((int) offset);
0900: } else {
0901: transactionFile.writeLong(offset);
0902: }
0903: if (absoluteOffset) {
0904: slotMask |= 1 << slotCounter;//absolute address
0905: }
0906: }
0907: if (slotCounter != 0) {//write trailing mask entry
0908: transactionFile.writeByte(slotMask);
0909: slotCounter = 0;
0910: slotMask = 0;
0911: }
0912: }
0913:
0914: transactionFile.seek(objectEndOffset);
0915: return tHandle.getTransactionOffset();
0916: }
0917:
0918: private static void deleteObject(IOTicket ioTicket,
0919: JODBSession session, TransactionHandle transactionHandle,
0920: TransactionContainer tContainer) throws IOException {
0921: PersistentObjectHandle handle = transactionHandle.getHandle();
0922: long persistentObjectOffset = handle.getObjectEntryOffset();
0923: transactionHandle.setTransactionOffset(0);
0924: deleteObject(ioTicket, session, persistentObjectOffset,
0925: tContainer);
0926: }
0927:
0928: private static void deleteObject(IOTicket ioTicket,
0929: JODBSession session, long persistentObjectOffset,
0930: TransactionContainer tContainer) throws IOException {
0931: //ioTicket.getRandomAccessBuffer().seek(persistentObjectOffset);
0932: IRandomAccessDataBuffer replacementsFile = tContainer
0933: .getTransactionReplacementsDataFile();
0934: DataContainersCache dataContainersCache = TransactionUtils
0935: .getObjectDataContainerCache();
0936: ObjectDataContainer container = dataContainersCache
0937: .pullObjectDataContainer();
0938: //JODBIOUtils.readObjectHeader(ioTicket, container, false);
0939: container.readHeader(ioTicket.getRandomAccessBuffer(),
0940: persistentObjectOffset, false);
0941: replacementsFile
0942: .writeByte(TRANSACTION_REPLACEMENT_ENTRY_TYPE_STATIC);
0943: replacementsFile.writeLong(persistentObjectOffset);//write offset of object to replace
0944: replacementsFile.writeLong(0);//reserving space for entry's length
0945: long entryOffsetStart = replacementsFile.getCursorOffset();
0946: TransactionUtils.writeEmptyObjectEntry(replacementsFile,
0947: container.getBodyLength());
0948: long entryEnd = replacementsFile.getCursorOffset();
0949: long entryLength = entryEnd - entryOffsetStart;
0950: replacementsFile.seek(entryOffsetStart - 8);//go back to entry's length
0951: replacementsFile.writeLong(entryLength);
0952: if (container.isRedirection()) {
0953: persistentObjectOffset = container.getOffset();
0954: deleteObject(ioTicket, session, persistentObjectOffset,
0955: tContainer);
0956: }
0957: dataContainersCache.pushObjectDataContainer(container);
0958: replacementsFile.seek(entryEnd);
0959: }
0960:
0961: private static boolean checkActiveObjectUnchanged(
0962: IClassProcessor processor, JODBOperationContext context,
0963: Object obj, TransactionHandle tHandle,
0964: ObjectDataContainer persistentCopyObjectDataContainer,
0965: Vector<IndexingRecord> indexes) throws IOException,
0966: IllegalClassTypeException {
0967: //TODO add mask verification
0968: if (tHandle.isUnchanged()) {
0969: return true;
0970: }
0971: if (tHandle.isNewObject()) {
0972: return false;
0973: }
0974:
0975: FieldsIterator fieldsIterator = persistentCopyObjectDataContainer
0976: .readObject(context, tHandle.getHandle()
0977: .getObjectEntryOffset(), true, indexes);
0978: if (fieldsIterator == null) {
0979: return false;
0980: }
0981: return processor.equals(obj, persistentCopyObjectDataContainer,
0982: context, null);
0983:
0984: }
0985:
0986: private static class ByteHolder {
0987: public byte _value;
0988:
0989: public ByteHolder() {
0990: }
0991:
0992: /**
0993: * @param value
0994: */
0995: public ByteHolder(byte value) {
0996: super ();
0997: _value = value;
0998: }
0999:
1000: }
1001:
1002: private static class ObjectFieldRecord {
1003: public Field _field;
1004: public Object _value;
1005: public long _offset;
1006:
1007: public ObjectFieldRecord(Field field, Object value) {
1008: super();
1009: _field = field;
1010: _value = value;
1011: }
1012:
1013: }
1014: }
|