/*
Copyright (C) 2007 Mobixess Inc. http://www.java-objects-database.com
This file is part of the JODB (Java Objects Database) open source project.
JODB is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation.
JODB is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
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.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import com.mobixess.jodb.core.ITransactionResolver;
import com.mobixess.jodb.core.IllegalClassTypeException;
import com.mobixess.jodb.core.JODBConfig;
import com.mobixess.jodb.core.JODBSessionContainer;
import com.mobixess.jodb.core.JodbIOException;
import com.mobixess.jodb.core.agent.JODBAgent;
import com.mobixess.jodb.core.index.IndexingRecord;
import com.mobixess.jodb.core.index.JODBIndexingAgent;
import com.mobixess.jodb.core.io.IOBase;
import com.mobixess.jodb.core.io.IRandomAccessDataBuffer;
import com.mobixess.jodb.core.io.JODBOperationContext;
import com.mobixess.jodb.core.io.IOBase.ObjectIdentity;
import com.mobixess.jodb.core.io.IRandomAccessBufferFactory.BUFFER_TYPE;
import com.mobixess.jodb.core.transaction.JODBSession.ClassDescriptor;
import com.mobixess.jodb.core.transaction.JODBSession.FieldAndIDRecord;
import com.mobixess.jodb.util.Utils;
/**
* @author Mobixess
*
*/
public class TransactionContainer implements ITranslatedDataSorce{
private Map<Object, TransactionHandle> _transactionObjects;
private Map<Object, TransactionHandle> _agentsTransactionObjects;
private WeakHashMap<ITransactionListener, Object> _transactionListeners = new WeakHashMap<ITransactionListener, Object>();
private JODBSession _session;
private IRandomAccessDataBuffer _newDataBuffer;
private IRandomAccessDataBuffer _replacementsBuffer;
private IRandomAccessDataBuffer _rollbackBuffer;
private ITransactionResolver _transactionResolver;
private int _agentsMode;
private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();
protected Logger _logger = Utils.getLogger(getClass().getName());
public TransactionContainer(JODBSession session, ITransactionResolver transactionResolver) {
super();
if(JODBConfig.DEBUG){
_transactionObjects = new LinkedHashMap<Object, TransactionHandle>();
_agentsTransactionObjects = new LinkedHashMap<Object, TransactionHandle>();
}else{
_transactionObjects = new IdentityHashMap<Object, TransactionHandle>();
_agentsTransactionObjects = new IdentityHashMap<Object, TransactionHandle>();
}
_transactionResolver = transactionResolver;
_lock = session.getBase().getTransactionLock();
_session = session;
}
/*package*/ Map<Object, TransactionHandle> getTransactionObjects() {
return isAgentsMode()?_agentsTransactionObjects:_transactionObjects;
}
public ITransactionResolver getTransactionResolver() {
return _transactionResolver;
}
public void lockTransaction() throws IOException{
_lock.writeLock().lock();
_newDataBuffer = JODBConfig.getRandomAccessBufferFactory().createBuffer("", BUFFER_TYPE.NEW_DATA, true);
_rollbackBuffer = JODBConfig.getRandomAccessBufferFactory().createBuffer("", BUFFER_TYPE.ROLLBACK, true);
_replacementsBuffer = JODBConfig.getRandomAccessBufferFactory().createBuffer("", BUFFER_TYPE.REPLACEMENTS, true);
}
public TransactionHandle getHandleForObject(Object key){
// TODO add lock?
return isAgentsMode()?_agentsTransactionObjects.get(key):_transactionObjects.get(key);
}
public void resetTranslatedObjects(JODBSession session, long transactionShift){
if(isAgentsMode()){
throw new IllegalStateException();
}
resetTranslatedObjects0(session, _transactionObjects, transactionShift);
resetTranslatedObjects0(session, _agentsTransactionObjects, transactionShift);
}
public void addTransactionListener(ITransactionListener listener){
synchronized (_transactionListeners) {
_transactionListeners.put(listener, null);
}
}
public void setTransactionResolver(ITransactionResolver transactionResolver)
{
_transactionResolver = transactionResolver;
}
public void removeTransactionListeners(ITransactionListener listener){
synchronized (_transactionListeners) {
_transactionListeners.remove(listener);
}
}
void fireOnObjectSet(Object object, JODBSession session){
synchronized (_transactionListeners) {
Iterator<ITransactionListener> iterator = _transactionListeners.keySet().iterator();
while (iterator.hasNext()) {
ITransactionListener element = iterator.next();
element.onSet(object,session);
}
}
}
void fireOnCommitStarted(Object object, JODBSession session){
synchronized (_transactionListeners) {
Iterator<ITransactionListener> iterator = _transactionListeners.keySet().iterator();
while (iterator.hasNext()) {
ITransactionListener element = iterator.next();
element.onCommitStarted(object,session);
}
}
}
void fireOnCommitFinished(Object object, JODBSession session){
synchronized (_transactionListeners) {
Iterator<ITransactionListener> iterator = _transactionListeners.keySet().iterator();
while (iterator.hasNext()) {
ITransactionListener element = iterator.next();
element.onCommitFinished(object,session);
}
}
}
private void resetTranslatedObjects0(JODBSession session, Map<Object, TransactionHandle> transactionObjects, long transactionShift){
if(transactionObjects.isEmpty()){
return;
}
Iterator<Object> iter = transactionObjects.keySet().iterator();
while (iter.hasNext()) {
Object element = iter.next();
TransactionHandle tHandle = transactionObjects.get(element);
if(tHandle.isNewObject()){
PersistentObjectHandle persistentObjectHandle = session.createHandleForObject(element, tHandle.getTransactionOffset()+transactionShift, tHandle.getTranslatedObjectDataMask(),(byte) tHandle.getCyclicalVersionCounter(), tHandle.getIdentityHash() );
_session.putObject(element, persistentObjectHandle);
}
iter.remove();
}
//_transactionObjects.clear();
}
public void processTranslatedObjectsIndex(JODBOperationContext context, long transactionShift) throws IOException{
enableAgentMode();
try{
Iterator<Object> iter = _transactionObjects.keySet().iterator();
while (iter.hasNext()) {
Object element = iter.next();
TransactionHandle tHandle = _transactionObjects.get(element);
long offsetId;
if(tHandle.isNewObject()){
offsetId = tHandle.getTransactionOffset()+transactionShift;
}else{
offsetId = tHandle.getHandle().getObjectEntryOffset();
}
Vector<IndexingRecord> indexingRecords = tHandle.getIndexes();
if (indexingRecords != null) {
for (int i = 0; i < indexingRecords.size(); i++) {
IndexingRecord record = indexingRecords.elementAt(i);
ByteBuffer currentValue = record.getPersistedDataBuffer();
JODBIndexingAgent indexingAgent = record.getIndexingAgent();
if (currentValue.limit() != 0 && !indexingAgent.removeIndex(offsetId, currentValue, null)) {
throw new JodbIOException("Illegal index state: can't remove index");
}
ByteBuffer pendingValue = record.getPendingDataBuffer();
if (pendingValue.limit() == 0) {
throw new JodbIOException(
"indexing value unavailable for " + offsetId);
}
indexingAgent.insertIndex(offsetId, pendingValue, context);
if(!_agentsTransactionObjects.containsKey(indexingAgent)){
try {
set(indexingAgent, Integer.MAX_VALUE);
} catch (Exception e) {
throw new JodbIOException(e);
}
}
}
}
}
}finally{
disableAgentMode();
}
}
public void reset(){
try {
_newDataBuffer.delete();
_replacementsBuffer.delete();
_rollbackBuffer.delete();
_newDataBuffer = null;
_replacementsBuffer = null;
_rollbackBuffer = null;
_transactionObjects.clear();
_agentsMode = 0;
} catch (IOException e) {
throw new RuntimeException(e);
}finally{
_lock.writeLock().unlock();
}
}
public void set(Object obj, int depth) throws IllegalClassTypeException, IllegalStateException{
_lock.readLock().lock();
try {
set0(obj, depth, JODBConfig.isGenerateCommitTimeStamp(), JODBConfig.isGenerateModificationTimeStamp(), isAgentsMode()?_agentsTransactionObjects:_transactionObjects);
} finally{
_lock.readLock().unlock();
}
}
public void runResolve(JODBSessionContainer sessionContainer) throws IOException{
Iterator<Object> iter = _transactionObjects.keySet().iterator();
IOBase base = _session.getBase();
ITransactionResolver transactionResolver = _transactionResolver;
while (iter.hasNext()) {
Object element = iter.next();
TransactionHandle tHandle = _transactionObjects.get(element);
if(!tHandle.isNewObject()){
PersistentObjectHandle handle = tHandle.getHandle();
int activeObjectHash = tHandle.getIdentityHash();
byte version = (byte) tHandle.getCyclicalVersionCounter();
ObjectIdentity objectIdentity = base.checkObjectChanged(handle.getObjectEntryOffset(), activeObjectHash, version);
if(objectIdentity!=null){
if(_transactionResolver!=null && !transactionResolver.resolve(element, sessionContainer,null)){
iter.remove();
continue;
}
tHandle.setIdentityHash(objectIdentity._hashIdentity);
tHandle.setCyclicalVersionCounter(objectIdentity._version);
}
}
}
}
public boolean isEmpty(){
return _transactionObjects.size() == 0 && _agentsTransactionObjects.size() == 0;
}
private synchronized void set0(Object obj, int depth, boolean creationTS, boolean modificationTS, Map<Object, TransactionHandle> transactionObjects ) throws IllegalClassTypeException{
if(depth <= 0){
return;
}
if(!isAgentsMode() && obj instanceof JODBAgent){
throw new IllegalArgumentException("Cannot accept agent object");
}
TransactionHandle transObjectHandle = transactionObjects.get(obj);
if(transObjectHandle != null){
transObjectHandle.enable_SET_TransactionState();
setChildren(obj, depth-1,creationTS,modificationTS, transactionObjects);
return;
}
fireOnObjectSet(obj, _session);
PersistentObjectHandle handle = _session.getHandleForActiveObject(obj);
if(handle!=null){
transObjectHandle = new TransactionHandle(handle);
}else{
transObjectHandle = new TransactionHandle(creationTS,modificationTS);
}
transObjectHandle.setAgentObjectMode( isAgentsMode() );
// if(JODBConfig.DEBUG){
// _logger.warning("setting transaction object "+obj);
// }
transactionObjects.put(obj, transObjectHandle);
transObjectHandle.enable_SET_TransactionState();
setChildren(obj, depth-1,creationTS,modificationTS, transactionObjects);
}
private void setChildren(Object obj, int depth, boolean creationTS, boolean modificationTS, Map<Object, TransactionHandle> transactionObjects) throws IllegalClassTypeException{
if(depth <= 0){
return;
}
ClassDescriptor descr = _session.getDescriptorForClass(obj.getClass());
if (descr.isArray()) {
if(!descr.isPrimitiveArray()){
int length = Array.getLength(obj);
for (int i = 0; i < length; i++) {
Object value = Array.get(obj, i);
if(value!=null){
set0(value, depth, creationTS, modificationTS, transactionObjects);
}
}
}
}else{
FieldAndIDRecord[] fields = descr.getAllFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i]._field;
if (field.getType().isPrimitive()) {
continue;
}
try {
Object value = field.get(obj);
if (value != null) {
set0(value, depth, creationTS, modificationTS, transactionObjects);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
public void delete(Object obj, int depth) throws IllegalClassTypeException{
_lock.readLock().lock();
try {
delete0(obj, depth);
} finally{
_lock.readLock().unlock();
}
}
public synchronized void delete0(Object obj, int depth) throws IllegalClassTypeException{
if(depth <= 0){
return;
}
TransactionHandle transObjectHandle = _transactionObjects.get(obj);
if(transObjectHandle != null){
if(transObjectHandle.isNewObject()){
_transactionObjects.remove(obj);
}else{
transObjectHandle.enable_DELETE_TransactionState();
}
deleteChildren(obj, depth-1);
return;
}
PersistentObjectHandle handle = _session.getHandleForActiveObject(obj);
if(handle!=null){
transObjectHandle = new TransactionHandle(handle);
transObjectHandle.enable_DELETE_TransactionState();
_transactionObjects.put(obj, transObjectHandle);
}
deleteChildren(obj, depth-1);
}
public synchronized void deleteChildren(Object obj, int depth) throws IllegalClassTypeException{
if(depth <= 0){
return;
}
ClassDescriptor descr = _session.getDescriptorForClass(obj.getClass());
FieldAndIDRecord[] fields = descr.getAllFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i]._field;
if(field.getType().isPrimitive()){
continue;
}
try {
Object value = field.get(obj);
if(value!=null){
delete0(value, depth);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public void resetTransactionBufferToStart() throws IOException{
_rollbackBuffer.resetToStart();
_newDataBuffer.resetToStart();
_replacementsBuffer.resetToStart();
}
public void resetTransactionBufferToEnd() throws IOException{
_rollbackBuffer.resetToEnd();
_newDataBuffer.resetToEnd();
_replacementsBuffer.resetToEnd();
}
public IRandomAccessDataBuffer getRollbackDataFile() {
return _rollbackBuffer;
}
public IRandomAccessDataBuffer getTransactionNewDataFile() {
return _newDataBuffer;
}
public IRandomAccessDataBuffer getTransactionReplacementsDataFile() {
return _replacementsBuffer;
}
public void enableAgentMode(){
_agentsMode++;
}
public void disableAgentMode(){
_agentsMode--;
if(_agentsMode<0){
throw new IllegalStateException();
}
}
public boolean isAgentsMode() {
return _agentsMode!=0;
}
}