/*
 NeoDatis ODB : Native Object Database (odb.info@neodatis.org)
 Copyright (C) 2007 NeoDatis Inc. http://www.neodatis.org

 "This file is part of the NeoDatis ODB open source object database".

 NeoDatis ODB is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 NeoDatis ODB 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
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.neodatis.odb.impl.core.transaction;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import org.neodatis.odb.ODBRuntimeException;
import org.neodatis.odb.OID;
import org.neodatis.odb.OdbConfiguration;
import org.neodatis.odb.core.ICoreProvider;
import org.neodatis.odb.core.NeoDatisError;
import org.neodatis.odb.core.layers.layer2.meta.CIZoneInfo;
import org.neodatis.odb.core.layers.layer2.meta.ClassInfo;
import org.neodatis.odb.core.layers.layer2.meta.MetaModel;
import org.neodatis.odb.core.layers.layer2.meta.ODBType;
import org.neodatis.odb.core.layers.layer2.meta.SessionMetaModel;
import org.neodatis.odb.core.layers.layer3.IBaseIdentification;
import org.neodatis.odb.core.layers.layer3.ICommitListener;
import org.neodatis.odb.core.layers.layer3.IOFileParameter;
import org.neodatis.odb.core.layers.layer3.IOSocketParameter;
import org.neodatis.odb.core.layers.layer3.IObjectWriter;
import org.neodatis.odb.core.layers.layer3.engine.IFileSystemInterface;
import org.neodatis.odb.core.transaction.ISession;
import org.neodatis.odb.core.transaction.ITransaction;
import org.neodatis.odb.core.transaction.IWriteAction;
import org.neodatis.odb.impl.core.layers.layer3.engine.LocalFileSystemInterface;
import org.neodatis.tool.DLogger;
import org.neodatis.tool.wrappers.OdbTime;
import org.neodatis.tool.wrappers.io.OdbFile;
import org.neodatis.tool.wrappers.list.IOdbList;
import org.neodatis.tool.wrappers.list.OdbArrayList;
import org.neodatis.tool.wrappers.map.OdbHashMap;

/**
 * 
 * The transaction class is used to guarantee ACID behavior. It keep tracks of all session
 * operations. It uses the WriteAction class to store all changes that can not be written to the file
 * before the commit.
 * The transaction is held by The Session class and manage commits and rollbacks.
 * 
 * All WriteActions are written in a transaction file to be sure to be able to commit and in case 
 * of very big transaction where all WriteActions can not be stored in memory.
 * 
* * @author osmadja * */ public class DefaultTransaction implements ITransaction { /** the log module name */ public static final String LOG_ID = "Transaction"; /** To indicate if transaction was confirmed = committed */ private boolean isCommited; /** The transaction creation time */ private long creationDateTime; /** * All the pending writing that must be applied to actually commit the * transaction */ private IOdbList writeActions; /** The same write action is reused for successive writes */ public IWriteAction currentWriteAction; /** The position of the next write for WriteAction */ public long currentWritePositionInWA; /** * To indicate if all write actions are in memory - if not, transaction must * read them from transaction file o commit the transaction */ private boolean hasAllWriteActionsInMemory; /** The number of write actions */ public int numberOfWriteActions; /** A file interface to the transaction file - used to read/write the file */ public IFileSystemInterface fsi; /** A file interface to the engine main file */ private IFileSystemInterface fsiToApplyWriteActions; /** To indicate if transaction has already been persisted in file */ private boolean hasBeenPersisted; /** * When this flag is set,the transaction will not be deleted, but will be * flagged as executed */ private boolean archiveLog; /** To indicate if transaction was rollbacked */ private boolean wasRollbacked; /** * A name to set the transaction file name. Used when reading transaction * file */ private String overrideTransactionName; /** To indicate if transaction is read only */ private boolean readOnlyMode; /** The transaction session */ public ISession session; /** To indicate is transaction is used for local or remote engine */ private boolean isLocal; private ICoreProvider provider; /** * The main constructor * * @param session * The transaction session * @throws IOException */ public DefaultTransaction(ISession session) throws IOException { init(session); } public DefaultTransaction(ISession session, String overrideTransactionName) throws IOException { this.overrideTransactionName = overrideTransactionName; init(session); this.readOnlyMode = true; } public DefaultTransaction(ISession session, IFileSystemInterface fsiToApplyTransaction) { this.fsiToApplyWriteActions = fsiToApplyTransaction; init(session); readOnlyMode = false; } public void init(ISession session) { this.provider = OdbConfiguration.getCoreProvider(); this.session = session; this.isCommited = false; creationDateTime = OdbTime.getCurrentTimeInMs(); writeActions = new OdbArrayList(1000); hasAllWriteActionsInMemory = true; numberOfWriteActions = 0; hasBeenPersisted = false; wasRollbacked = false; currentWritePositionInWA = -1; } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#clear() */ public void clear() { if (writeActions != null) { writeActions.clear(); writeActions = null; } } /** * Reset the transaction * * */ public void reset() { clear(); init(session); fsi = null; } /** * Adds a write action to the transaction * * @param writeAction * The write action to be added */ public void addWriteAction(IWriteAction writeAction) { addWriteAction(writeAction, true); } /** * Adds a write action to the transaction * * @param writeAction * The write action to be added * @param persistWriteAcion * To indicate if write action must be persisted */ public void addWriteAction(IWriteAction writeAction, boolean persistWriteAcion) { if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.info("Adding WA in Transaction of session " + session.getId()); } if (writeAction.isEmpty()) { return; } checkRollback(); if (!hasBeenPersisted && persistWriteAcion) { persist(); } if (persistWriteAcion) { writeAction.persist(fsi, numberOfWriteActions + 1); } // Only adds the writeaction to the list if the transaction keeps all in // memory if (hasAllWriteActionsInMemory) { writeActions.add(writeAction); } numberOfWriteActions++; if (hasAllWriteActionsInMemory && numberOfWriteActions > OdbConfiguration.getMaxNumberOfWriteObjectPerTransaction()) { hasAllWriteActionsInMemory = false; Iterator iterator = writeActions.iterator(); DefaultWriteAction wa = null; while (iterator.hasNext()) { wa = (DefaultWriteAction) iterator.next(); wa.clear(); } writeActions.clear(); if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.info("Number of objects has exceeded the max number " + numberOfWriteActions + "/" + OdbConfiguration.getMaxNumberOfWriteObjectPerTransaction() + ": switching to persistent transaction managment"); } } } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#getName() */ public String getName() { IBaseIdentification p = fsiToApplyWriteActions.getParameters(); if (p instanceof IOFileParameter) { IOFileParameter ifp = (IOFileParameter) fsiToApplyWriteActions.getParameters(); StringBuffer buffer = new StringBuffer(ifp.getCleanFileName()).append("-").append(creationDateTime).append("-").append( session.getId()).append(".transaction"); return buffer.toString(); } if (p instanceof IOSocketParameter) { IOSocketParameter sp = (IOSocketParameter) fsiToApplyWriteActions.getParameters(); return sp.getBaseIdentifier(); } throw new ODBRuntimeException(NeoDatisError.UNSUPPORTED_IO_TYPE.addParameter(p.getClass().getName())); } IBaseIdentification getParameters(boolean canWrite) { IBaseIdentification p = fsiToApplyWriteActions.getParameters(); if (p instanceof IOFileParameter) { IOFileParameter ifp = (IOFileParameter) fsiToApplyWriteActions.getParameters(); StringBuffer buffer = new StringBuffer(ifp.getDirectory()).append("/").append(ifp.getCleanFileName()).append("-").append(creationDateTime).append("-").append( session.getId()).append(".transaction"); return new IOFileParameter(buffer.toString(), canWrite, ifp.getUserName(), ifp.getPassword()); } if (p instanceof IOSocketParameter) { IOSocketParameter sp = (IOSocketParameter) fsiToApplyWriteActions.getParameters(); return new IOSocketParameter(sp.getDestinationHost(), sp.getPort(), sp.getBaseIdentifier(), IOSocketParameter.TYPE_TRANSACTION, creationDateTime, null, null); } throw new ODBRuntimeException(NeoDatisError.UNSUPPORTED_IO_TYPE.addParameter(p.getClass().getName())); } private void checkFileAccess(boolean canWrite) { checkFileAccess(canWrite, null); } private synchronized void checkFileAccess(boolean canWrite, String fileName) { if (fsi == null) { IBaseIdentification p = null; // to unable direct junit test of FileSystemInterface if (fsiToApplyWriteActions == null) { p = new IOFileParameter(fileName, canWrite,null,null); } else { p = getParameters(canWrite); } // To enable unit test if (session != null) { isLocal = session.getStorageEngine().isLocal(); } fsi = new LocalFileSystemInterface("transaction", session, p, false, OdbConfiguration.getDefaultBufferSizeForTransaction()); } } protected void persist() { checkFileAccess(true); try { if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.debug("# Persisting transaction " + getName()); } fsi.setWritePosition(0, false); fsi.writeBoolean(isCommited, false); fsi.writeLong(creationDateTime, false, "creation date", DefaultWriteAction.DIRECT_WRITE_ACTION); // Size fsi.writeLong(0, false, "size", DefaultWriteAction.DIRECT_WRITE_ACTION); hasBeenPersisted = true; } finally { } } public IOdbList getWriteActions() { return writeActions; } public long getCreationDateTime() { return creationDateTime; } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#isCommited() */ public boolean isCommited() { return isCommited; } /** * Mark te transaction file as committed * * @param isConfirmed */ private void setCommited(boolean isConfirmed) { this.isCommited = isConfirmed; checkFileAccess(true); try { // TODO Check atomicity // Writes the number of write actions after the byte and date fsi.setWritePositionNoVerification(ODBType.BYTE.getSize() + ODBType.LONG.getSize(), false); fsi.writeLong(numberOfWriteActions, false, "nb write actions", DefaultWriteAction.DIRECT_WRITE_ACTION); // FIXME The fsi.flush should not be called after the last write? fsi.flush(); // Only set useBuffer = false when it is a local database to avoid // net io overhead if (isLocal) { fsi.useBuffer(false); } fsi.setWritePositionNoVerification(0, false); fsi.writeByte((byte) 1, false); } finally { } } private void checkRollback() { if (wasRollbacked) { throw new ODBRuntimeException(NeoDatisError.ODB_HAS_BEEN_ROLLBACKED); } } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#rollback() */ public void rollback() { wasRollbacked = true; if (fsi != null) { fsi.close(); delete(); } } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#commit() */ public void commit() { if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.info("Commiting " + numberOfWriteActions + " write actions - In Memory : " + hasAllWriteActionsInMemory + " - sid=" + session.getId()); } // Check if database has been rollbacked checkRollback(); // call the commit listeners manageCommitListenersBefore(); if (currentWriteAction != null && !currentWriteAction.isEmpty()) { addWriteAction(currentWriteAction); currentWriteAction = null; } if (fsi == null && numberOfWriteActions != 0) { throw new ODBRuntimeException(NeoDatisError.TRANSACTION_ALREADY_COMMITED_OR_ROLLBACKED); } if (numberOfWriteActions == 0 || readOnlyMode) { // FIXME call commitMetaModel in realOnlyMode? commitMetaModel(); // Nothing to do if (fsi != null) { fsi.close(); fsi = null; } if (session != null) { session.getCache().clearOnCommit(); } return; } // Marks the transaction as committed setCommited(true); // Apply the write actions the main database file applyTo(); // Commit Meta Model changes commitMetaModel(); if (archiveLog) { fsi.setWritePositionNoVerification(0, false); fsi.writeByte((byte) 2, false); fsi.getIo().enableAutomaticDelete(false); fsi.close(); fsi = null; } else { fsi.close(); delete(); fsi = null; } if (session != null) { session.getCache().clearOnCommit(); } manageCommitListenersAfter(); } private void manageCommitListenersAfter() { IOdbList listeners = session.getStorageEngine().getCommitListeners(); if (listeners == null || listeners.isEmpty()) { return; } Iterator iterator = listeners.iterator(); ICommitListener commitListener = null; while (iterator.hasNext()) { commitListener = iterator.next(); commitListener.afterCommit(); } } private void manageCommitListenersBefore() { IOdbList listeners = session.getStorageEngine().getCommitListeners(); if (listeners == null || listeners.isEmpty()) { return; } Iterator iterator = listeners.iterator(); ICommitListener commitListener = null; while (iterator.hasNext()) { commitListener = iterator.next(); commitListener.beforeCommit(); } } /** * Used to commit meta model : classes This is useful when running in client * server mode TODO Check this */ protected void commitMetaModel() { MetaModel sessionMetaModel = session.getMetaModel(); // If meta model has not been modified, there is nothing to do if (!sessionMetaModel.hasChanged()) { return; } if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.debug("Start commitMetaModel"); } MetaModel lastCommitedMetaModel = new SessionMetaModel(); if (isLocal) { // In local mode, we must not reload the meta model as there is no // concurrent access lastCommitedMetaModel = sessionMetaModel; } else { // In ClientServer mode, re-read the meta-model from the database // base to get last update. lastCommitedMetaModel = session.getStorageEngine().getObjectReader().readMetaModel(lastCommitedMetaModel, false); } // Gets the classes that have changed (that have modified ,deleted or // inserted objects) Iterator cis = sessionMetaModel.getChangedClassInfo().iterator(); ClassInfo newCi = null; ClassInfo lastCommittedCI = null; IObjectWriter writer = session.getStorageEngine().getObjectWriter(); OID lastCommittedObjectOIDOfThisTransaction = null; OID lastCommittedObjectOIDOfPrevTransaction = null; // for all changes between old and new meta model while (cis.hasNext()) { newCi = cis.next(); if (lastCommitedMetaModel.existClass(newCi.getFullClassName())) { // The last CI represents the last committed meta model of the // database lastCommittedCI = lastCommitedMetaModel.getClassInfoFromId(newCi.getId()); // Just be careful to keep track of current CI committed zone // deleted objects lastCommittedCI.getCommitedZoneInfo().setNbDeletedObjects(newCi.getCommitedZoneInfo().getNbDeletedObjects()); } else { lastCommittedCI = newCi; } lastCommittedObjectOIDOfThisTransaction = newCi.getCommitedZoneInfo().last; lastCommittedObjectOIDOfPrevTransaction = lastCommittedCI.getCommitedZoneInfo().last; /* * Take last committed number of objects and sets to CI * newCi.getCommitedZoneInfo().nbObjects = * lastCommittedCI.getCommitedZoneInfo().nbObjects; * newCi.getCommitedZoneInfo().first = * lastCommittedCI.getCommitedZoneInfo().first; * newCi.getCommitedZoneInfo().last = * lastCommittedCI.getCommitedZoneInfo().last; */ OID lastCommittedObjectOID = lastCommittedObjectOIDOfPrevTransaction; // If some object have been created then if (lastCommittedObjectOIDOfPrevTransaction != null) { // Checks if last object of committed meta model has not been // deleted if (session.getCache().isDeleted(lastCommittedObjectOIDOfPrevTransaction)) { // TODO This is wrong: if a committed transaction deleted a // committed object and creates x new // objects, then all these new objects will be lost: // if it has been deleted then use the last object of the // session class info lastCommittedObjectOID = lastCommittedObjectOIDOfThisTransaction; newCi.getCommitedZoneInfo().last = lastCommittedObjectOID; } } // Connect Unconnected zone to connected zone // make next oid of last committed object point to first // uncommitted object // make previous oid of first uncommitted object point to // last committed object if (lastCommittedObjectOID != null && newCi.getUncommittedZoneInfo().hasObjects()) { if (newCi.getCommitedZoneInfo().hasObjects()) { // these 2 updates are executed directly without // transaction, because // We are in the commit process. writer.updateNextObjectFieldOfObjectInfo(lastCommittedObjectOID, newCi.getUncommittedZoneInfo().first, false); writer.updatePreviousObjectFieldOfObjectInfo(newCi.getUncommittedZoneInfo().first, lastCommittedObjectOID, false); } else { // Committed zone has 0 object writer.updatePreviousObjectFieldOfObjectInfo(newCi.getUncommittedZoneInfo().first, null, false); } } // The number of committed objects must be updated with the number // of the last committed CI because a transaction may have been // committed changing this number. // Notice that the setNbObjects receive the full CommittedCIZoneInfo // object // because it will set the number of objects and the number of // deleted objects newCi.getCommitedZoneInfo().setNbObjects(lastCommittedCI.getCommitedZoneInfo()); // and don't forget to set the deleted objects // This sets the number of objects, the first object OID and the // last object OID newCi = buildClassInfoForCommit(newCi); writer.updateInstanceFieldsOfClassInfo(newCi, false); if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.debug("Analysing class " + newCi.getFullClassName()); DLogger.debug("\t-Commited CI = " + newCi); DLogger.debug("\t-connect last commited object with oid " + lastCommittedObjectOID + " to first uncommited object " + newCi.getUncommittedZoneInfo().first); DLogger.debug("\t-Commiting new Number of objects = " + newCi.getNumberOfObjects()); } } sessionMetaModel.resetChangedClasses(); // To guarantee integrity after commit, the meta model is set to null // If the user continues using odb instance after commit the meta model // will be lazy-reloaded. Only for Client Server mode if (!isLocal) { session.setMetaModel(null); } } /** * Shift all unconnected infos to connected (committed) infos * * @param classInfo * @return The updated class info */ public ClassInfo buildClassInfoForCommit(ClassInfo classInfo) { long nbObjects = classInfo.getNumberOfObjects(); classInfo.getCommitedZoneInfo().setNbObjects(nbObjects); if (classInfo.getCommitedZoneInfo().first != null) { // nothing to change } else { classInfo.getCommitedZoneInfo().first = classInfo.getUncommittedZoneInfo().first; } if (classInfo.getUncommittedZoneInfo().last != null) { classInfo.getCommitedZoneInfo().last = classInfo.getUncommittedZoneInfo().last; } // Resets the unconnected zone info classInfo.getUncommittedZoneInfo().set(new CIZoneInfo(classInfo, null, null, 0)); return classInfo; } public static DefaultTransaction read(String fileName) throws IOException, ClassNotFoundException { OdbFile file = new OdbFile(fileName); if(!file.exists()){ throw new ODBRuntimeException(NeoDatisError.FILE_NOT_FOUND.addParameter(fileName)); } // @TODO check this DefaultTransaction transaction = new DefaultTransaction(null, fileName); transaction.loadWriteActions(fileName, false); return transaction; } public void loadWriteActions(boolean apply) { loadWriteActions(getName(), apply); } public void loadWriteActions(String filename, boolean apply) { if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.debug("Load write actions of " + filename); } DefaultWriteAction wa = null; try { checkFileAccess(false, filename); fsi.useBuffer(true); fsi.setReadPosition(0); isCommited = fsi.readByte() == 1; creationDateTime = fsi.readLong(); long totalNumberOfWriteActions = fsi.readLong(); if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.info(writeActions.size() + " write actions in file"); } for (int i = 0; i < totalNumberOfWriteActions; i++) { wa = DefaultWriteAction.read(fsi, i + 1); if (apply) { wa.applyTo(fsiToApplyWriteActions, i + 1); wa.clear(); } else { addWriteAction(wa, false); } } if (apply) { fsiToApplyWriteActions.flush(); } } finally { } } public void loadWriteActionsBackwards(String filename, boolean apply) throws IOException, ClassNotFoundException { int executedWriteAction = 0; if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.debug("Load write actions of " + filename); } IWriteAction wa = null; try { checkFileAccess(false, filename); fsi.useBuffer(true); fsi.setReadPosition(0); isCommited = fsi.readByte() == 1; creationDateTime = fsi.readLong(); Map writtenPositions = null; if (apply) { writtenPositions = new OdbHashMap(); } Long position = new Long(-1); int i = numberOfWriteActions; long previousWriteActionPosition = fsi.getLength(); while (i > 0) { // Sets the position 8 bytes backwards fsi.setReadPosition(previousWriteActionPosition - ODBType.LONG.getSize()); // And then the read a long, this will be the previous write // action position previousWriteActionPosition = fsi.readLong(); // Then sets the read position to read the write action fsi.setReadPosition(previousWriteActionPosition); wa = DefaultWriteAction.read(fsi, i + 1); if (apply) { position = new Long(wa.getPosition()); if (writtenPositions.get(position) != null) { // It has already been written something more recent at // this position, do not write again i--; continue; } wa.applyTo(fsiToApplyWriteActions, i + 1); writtenPositions.put(position, position); executedWriteAction++; } else { addWriteAction(wa, false); } i--; } if (apply) { fsiToApplyWriteActions.flush(); if (OdbConfiguration.isDebugEnabled(LOG_ID)) { DLogger.debug("Total Write actions : " + i + " / position cache = " + writtenPositions.size()); } DLogger.info("Total write actions = " + numberOfWriteActions + " : executed = " + executedWriteAction); writtenPositions.clear(); writtenPositions = null; } } finally { } } /** * deletes the transaction file * * @throws IOException */ protected void delete() { // The delete is done automatically by underlying api } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("state=").append(isCommited).append(" | creation=").append(creationDateTime).append(" | write actions numbers=") .append(numberOfWriteActions); return buffer.toString(); } private void applyTo() { int realWriteNumber = 0; int noPointerWA = 0; if (!isCommited) { DLogger.info("can not execute a transaction that is not confirmed"); return; } if (hasAllWriteActionsInMemory) { for (int i = 0; i < writeActions.size(); i++) { DefaultWriteAction wa = (DefaultWriteAction) writeActions.get(i); wa.applyTo(fsiToApplyWriteActions, i + 1); wa.clear(); } fsiToApplyWriteActions.flush(); } else { loadWriteActions(true); fsiToApplyWriteActions.flush(); } } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#setFsiToApplyWriteActions(org.neodatis.odb.core.impl.layers.layer3.engine.FileSystemInterface) */ public void setFsiToApplyWriteActions(IFileSystemInterface fsi) { this.fsiToApplyWriteActions = fsi; } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#isArchiveLog() */ public boolean isArchiveLog() { return archiveLog; } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#setArchiveLog(boolean) */ public void setArchiveLog(boolean archiveLog) { this.archiveLog = archiveLog; } /** * @return Returns the numberOfWriteActions. */ public int getNumberOfWriteActions() { if (currentWriteAction != null && !currentWriteAction.isEmpty()) { return numberOfWriteActions + 1; } return numberOfWriteActions; } public IFileSystemInterface getFsi() throws IOException { if (fsi == null) { checkFileAccess(!readOnlyMode); } return fsi; } /** * Set the write position (position in main database file). This is used to * know if the next write can be appended to the previous one (in the same * current Write Action) or not. * * @param position */ public void setWritePosition(long position) { if (position != this.currentWritePositionInWA) { this.currentWritePositionInWA = position; if (currentWriteAction != null) { addWriteAction(currentWriteAction); } this.currentWriteAction = new DefaultWriteAction(position); } else { if (currentWriteAction == null) { this.currentWriteAction = new DefaultWriteAction(position); this.currentWritePositionInWA = position; } } } /* * (non-Javadoc) * * @see org.neodatis.odb.core.impl.transaction.ITransaction#manageWriteAction(long, * byte[]) */ public void manageWriteAction(long position, byte[] bytes) { if (this.currentWritePositionInWA == position) { if (currentWriteAction == null) { currentWriteAction = provider.getWriteAction(position, null); } currentWriteAction.addBytes(bytes); this.currentWritePositionInWA += bytes.length; } else { if (currentWriteAction != null) { addWriteAction(currentWriteAction); } this.currentWriteAction = provider.getWriteAction(position, bytes); this.currentWritePositionInWA = position + bytes.length; } } }

Ads help maintain this website.