/*
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.DataOutput;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.Vector;
import java.util.WeakHashMap;
import com.mobixess.jodb.core.JODBConfig;
import com.mobixess.jodb.core.JodbIOException;
import com.mobixess.jodb.core.index.IndexingRecord;
import com.mobixess.jodb.core.io.IOBase;
import com.mobixess.jodb.core.io.IOTicket;
import com.mobixess.jodb.core.io.IRandomAccessDataBuffer;
import com.mobixess.jodb.core.io.JODBIOBase;
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.util.ArrayUtils;
import com.mobixess.jodb.util.Utils;
/**
* @author Mobixess
*
*/
public class TransactionUtils{
private final static int MAX_DATA_CONTAINERS_CACHE = 100;
// private static class LaunchObjectsLevelsCache{
// private Vector<WeakReference<Vector<FieldRecord>>> _cache = new Vector<WeakReference<Vector<FieldRecord>>>();
//
// public Vector<FieldRecord> getFieldRecordCache(int level){
// if(level >= _cache.size()){
// _cache.setSize(level+1);
// }
// WeakReference<Vector<FieldRecord>> weakReference = _cache.elementAt(level);
// Vector<FieldRecord> result = null;
// if(weakReference == null || (result = weakReference.get()) == null){
// result = new Vector<FieldRecord>();
// weakReference = new WeakReference<Vector<FieldRecord>>(result);
// _cache.setElementAt(weakReference, level);
// }
// result.setSize(0);
// return result;
// }
// }
@SuppressWarnings("unchecked")
public static class DataContainersCache{
private WeakRefObjectDataContainerCache _objectDataContainerCache = new WeakRefObjectDataContainerCache();
private WeakRefIndexContainerCache _weakRefIndexContainerCache = new WeakRefIndexContainerCache();
private WeakRefVectorCache _weakRefVectorCache = new WeakRefVectorCache();
private DataContainersCache() {
}
public ObjectDataContainer pullObjectDataContainer(){
return _objectDataContainerCache.pull();
}
public void pushObjectDataContainer(ObjectDataContainer container){
_objectDataContainerCache.push(container);
}
public IndexingRecord pullIndexRecord(){
return _weakRefIndexContainerCache.pull();
}
public void pushIndexRecord(IndexingRecord byteBuffer){
_weakRefIndexContainerCache.push(byteBuffer);
}
public void pushVector(Vector container){
_weakRefVectorCache.push(container);
}
public Vector pullVector(){
return _weakRefVectorCache.pull();
}
}
private static class WeakRefObjectDataContainerCache extends WeakRefDataContainerCache<ObjectDataContainer> {
public WeakRefObjectDataContainerCache() {
super(MAX_DATA_CONTAINERS_CACHE);
}
@Override
public ObjectDataContainer pull()
{
ObjectDataContainer result = super.pull();
if(result == null){
result = new ObjectDataContainer();
}
return result;
}
@Override
public void push(ObjectDataContainer container)
{
container.reset();
super.push(container);
}
}
private static class WeakRefVectorCache extends WeakRefDataContainerCache<Vector> {
public WeakRefVectorCache() {
super(10);
}
@Override
public Vector pull()
{
Vector result = super.pull();
if(result == null){
result = new Vector(2);
}
return result;
}
@Override
public void push(Vector container)
{
container.clear();
super.push(container);
}
}
private static class WeakRefIndexContainerCache extends WeakRefDataContainerCache<IndexingRecord> {
public WeakRefIndexContainerCache() {
super(4);
}
@Override
public IndexingRecord pull()
{
IndexingRecord indexingRecord = super.pull();
if(indexingRecord == null){
indexingRecord = new IndexingRecord();
}
indexingRecord.reset();
return indexingRecord;
}
}
private static class WeakRefDataContainerCache <ContainerType> {
private WeakReference<ContainerType>[] _pull;
private int _index;
@SuppressWarnings("unchecked")
public WeakRefDataContainerCache(int size) {
_pull = new WeakReference[size];
}
public ContainerType pull(){
WeakReference<ContainerType> reference;
for (; _index>=0; _index--) {
reference = _pull[_index];
if (reference!=null) {
ContainerType result = reference.get();
_pull[_index] = null;
if (result != null) {
return result;
}
}
}
return null;
}
public void push(ContainerType container){
if(_index==_pull.length-1){
return;
}
_pull[++_index]=new WeakReference<ContainerType>(container);
}
}
// private static ThreadLocal<WeakReference<LaunchObjectsLevelsCache>> _launchObjectCache = new ThreadLocal<WeakReference<LaunchObjectsLevelsCache>>() {
// @Override
// protected WeakReference<LaunchObjectsLevelsCache> initialValue()
// {
// return new WeakReference<LaunchObjectsLevelsCache>(new LaunchObjectsLevelsCache());
// }
// };
private static ThreadLocal<WeakReference<DataContainersCache>> _objectDataContainerCacheWeakRef = new ThreadLocal<WeakReference<DataContainersCache>>(){
@Override
protected WeakReference<DataContainersCache> initialValue()
{
return new WeakReference<DataContainersCache>(new DataContainersCache());
}
};
private static ThreadLocal<OperationContextTicketCache> _threadLocalTicketCacheHolder = new ThreadLocal<OperationContextTicketCache>(){
@Override
protected OperationContextTicketCache initialValue()
{
return new OperationContextTicketCache();
}
};
public static boolean compareToPersistentValues(JODBSession session, Field[] fields, int[] fieldsIDs, Object currentObject, IOTicket ioTicket, ObjectDataContainer headerData ) throws IOException{
boolean[] processedFields = new boolean[fieldsIDs.length];
IRandomAccessDataBuffer in = ioTicket.getRandomAccessBuffer();
short classNameIDs = in.readShort();
for (int i = 0; i < classNameIDs; i++) {
in.readShort();
}
//null fields no longer persisted
// if(headerData.hasNullFields()){
// int nullFieldsTotal = in.readShort()&0xffff;
// for (int i = 0; i < nullFieldsTotal; i++) {
// int id = in.readShort()&0xffff;
// int index = ArrayUtils.indexOf(fieldsIDs, id);
// if(index == -1){
// continue;
// }
// Field field = fields[index];
// try {
// if(field.get(currentObject)!=null){//TODO may need additional check if object is in transaction, rare case though
// return false;
// }
// } catch (Exception e) {
// e.printStackTrace();
// throw new IOException(e.getMessage());
// }
// processedFields[index] = true;
// }
// }
if(headerData.hasDirectlyAddressedFields()){
boolean result = verifyLinks(session, false, fields, fieldsIDs, processedFields, currentObject, ioTicket);
if(!result){
return false;
}
}
if(headerData.hasRelativelyAddressedFields()){
boolean result = verifyLinks(session, true, fields, fieldsIDs, processedFields, currentObject, ioTicket);
if(!result){
return false;
}
}
//long endOffset = headerData.getOffset() + headerData.getTotalLength();
if (headerData.hasPrimitiveFields()) {
int total = in.readShort()&0xFFFF;
for (int i = 0; i < total; i++) {
int id = in.readShort() & 0xffff;
int index = ArrayUtils.indexOf(fieldsIDs, id);
Field field = fields[index];
if (index == -1) {
Utils.skipPrimitive(field, ioTicket.getRandomAccessBuffer());
continue;
}
if (!Utils.readPrimitiveAndCompare(currentObject, field,in)){
return false;
}
processedFields[index] = true;
}
}
for (int i = 0; i < fieldsIDs.length; i++) {//cheking if remaining not found fields have default values
if(fieldsIDs[i]==-1||processedFields[i]){
continue;
}
Field field = fields[i];
if(field.getType().isPrimitive()){
if(!Utils.primitiveEquals(field, currentObject, 0)){
return false;
}
}else{
Object value;
try {
value = field.get(currentObject);
} catch (Exception e) {
e.printStackTrace();
throw new JodbIOException(e);
}
if(value!=null){
return false;
}
}
}
return true;
}
public static void writeEmptyObjectEntry(DataOutput dataOutput,long len) throws IOException{
if(len<=0xff){
dataOutput.writeShort(JODBIOBase.ENTRY_EMPTY_ID|JODBIOBase.LEN_MODIFIER_BYTE);
dataOutput.write((int) len);
}else if(len <= 0xffff){
dataOutput.writeShort(JODBIOBase.ENTRY_EMPTY_ID);
dataOutput.writeShort((int) len);
}else{
dataOutput.writeShort(JODBIOBase.ENTRY_EMPTY_ID|JODBIOBase.LEN_MODIFIER_LONG);
dataOutput.writeLong(len);
}
}
public static void writeRedirectionEntry(DataOutput dataOutput,long len, long offset, boolean skipLength) throws IOException{
if(len<=0xff){
dataOutput.write(JODBIOBase.ENTRY_REDIRECTOR_ID|JODBIOBase.LEN_MODIFIER_BYTE);
dataOutput.write((int) len);
}else if(len <= 0xffff){
dataOutput.writeShort(JODBIOBase.ENTRY_REDIRECTOR_ID);
dataOutput.writeShort((int) len);
}else{
dataOutput.writeShort(JODBIOBase.ENTRY_REDIRECTOR_ID|JODBIOBase.LEN_MODIFIER_LONG);
dataOutput.writeLong(len);
}
dataOutput.writeLong(offset);
}
private static boolean verifyLinks(JODBSession session, boolean isRelativeAddr, Field[] fields, int[] fieldsIDs, boolean[] processedFields, Object currentObject, IOTicket ioTicket) throws IOException{
IRandomAccessDataBuffer in = ioTicket.getRandomAccessBuffer();
int directlyAddressedFieldsTotal = in.readShort()&0xffff;
for (int i = 0; i < directlyAddressedFieldsTotal; i++) {
int id = in.readShort()&0xffff;
long offset;
if(isRelativeAddr){
offset = in.readInt()+ in.getCursorOffset();
}else{
offset = in.readLong();
}
int index = ArrayUtils.indexOf(fieldsIDs, id);
if(index == -1){
continue;
}
Field field = fields[index];
try {
Object value = field.get(currentObject);
if(value == null){//this field can't be null
return false;
}
PersistentObjectHandle handle = session.getHandleForActiveObject(value);
if(handle == null){//this field must contain known object
return false;
}
if(handle.getObjectEntryOffset() == offset){//same offset - object verified
processedFields[index] = true;
continue;
}
} catch (Exception e) {
e.printStackTrace();
throw new JodbIOException(e);
}
}
return true;
}
public static Object launchObject(JODBSession session, long offset, Object activationInstance, int remainingDepth) throws IOException{
if(remainingDepth < 0 || offset == 0){
return null;
}
IOBase base = session.getBase();
boolean delayedActivation = activationInstance != null;
Object instance;
if (!delayedActivation) {//this is user's requested activation call, instance already exist
instance = session.getObjectFromCache(offset);
if (instance != null) {
return instance;
}
}else{
instance = activationInstance;
}
OperationContextTicketCache contextTicketCache = _threadLocalTicketCacheHolder.get();
IOTicket ioTicket = contextTicketCache.getTicketForRead(base);//base.getIOTicket(true, false);
try{
ioTicket.lock(false);
DataContainersCache dataContainersCache = getObjectDataContainerCache();
ObjectDataContainer objectDataContainer = dataContainersCache.pullObjectDataContainer();
FieldsIterator fieldsIterator = objectDataContainer.readObject(ioTicket.getRandomAccessBuffer(),base, session, offset, true);
ClassDescriptor classDescriptor = objectDataContainer.getClassDescriptorForPersistedObject();
if (classDescriptor == null || fieldsIterator == null) {
// basically same as >>objectDataContainer.isDeleted()<< or no class available for persistent copy
return null;
}
IClassProcessor processor;
if (!delayedActivation) {
int classTypeId = objectDataContainer.getOriginalClassType();
String className = base.getClassTypeForID(classTypeId);
Class clazz;
try {
clazz = session.resolveClassForName(className);
} catch (ClassNotFoundException e) {
return null;
}
processor = JODBPluginRegistry.getInstance().getClassProcessor(clazz);
instance = processor.composeInstance(clazz, objectDataContainer, session);
}else{
processor = JODBPluginRegistry.getInstance().getClassProcessor(instance.getClass());
}
PersistentObjectHandle handle = session.createHandleForObject(instance, offset, objectDataContainer);
synchronized (handle) {//TODO "get object" must obey this lock or incompletely initialized object could become accessible
if(!delayedActivation){
synchronized (session.getActivationSynchObject()) {//put object into cache to prevent potential recursion
//check if some other thread already instantiated the object
Object candidate = session.getObjectFromCache(offset);
if (candidate != null) {
dataContainersCache.pushObjectDataContainer(objectDataContainer);//return container to cache
return candidate;
}
session.putObject(instance, handle);
}
}
processor.activate(instance, objectDataContainer, session, remainingDepth, delayedActivation);
dataContainersCache.pushObjectDataContainer(objectDataContainer);//return container to cache
objectDataContainer = null;
}
}finally{
ioTicket.unlock();
contextTicketCache.putToCache(ioTicket);
}
return instance;
}
public void deactivate(ClassDescriptor classDescriptor, Object obj, int depth) throws IOException {
deactivate0(classDescriptor, obj, depth, 0);
}
private void deactivate0(ClassDescriptor classDescriptor, Object obj, int depth, int level) throws IOException {
//Vector<FieldRecord> records = getFieldsContainerForLevel(level);
//TODO
}
public static DataContainersCache getObjectDataContainerCache(){
WeakReference<DataContainersCache> reference = _objectDataContainerCacheWeakRef.get();
DataContainersCache cache = reference.get();
if(cache == null){
cache = new DataContainersCache();
_objectDataContainerCacheWeakRef.set(new WeakReference<DataContainersCache>(cache));
}
return cache;
}
public static OperationContextTicketCache getContextTicketCache(){
return _threadLocalTicketCacheHolder.get();
}
// private static Vector<FieldRecord> getFieldsContainerForLevel(int level){
// WeakReference<LaunchObjectsLevelsCache> launchObjectsLevelsCache = _launchObjectCache.get();
// LaunchObjectsLevelsCache cache = launchObjectsLevelsCache.get();
// if(cache == null){
// cache = new LaunchObjectsLevelsCache();
// launchObjectsLevelsCache = new WeakReference<LaunchObjectsLevelsCache>(cache);
// _launchObjectCache.set(launchObjectsLevelsCache);
// }
// return cache.getFieldRecordCache(level);
// }
public static class OperationContextTicketCache{//this class safe for threadlocal context only
private Vector<IOTicket> _ioTicketsCache;
public void resetCache() {
_ioTicketsCache = null;
}
public void setIoTicketsCache(Vector<IOTicket> ioTicketsCache) {
_ioTicketsCache = ioTicketsCache;
}
IOTicket getTicketForRead(IOBase base) throws IOException{
if(_ioTicketsCache!=null && _ioTicketsCache.size() > 0){
int index = _ioTicketsCache.size()-1;
IOTicket result = _ioTicketsCache.elementAt(index);
if(result.getBase() != base){
if(JODBConfig.DEBUG){
throw new IOException("Illegal Cache Content");
}
_ioTicketsCache = null;// clean cache
}else{
_ioTicketsCache.setSize(index);
return result;
}
}
return base.getIOTicket(true, false);
}
void putToCache(IOTicket ticket) throws IOException{
if(_ioTicketsCache!=null){
_ioTicketsCache.add(ticket);
}else{
ticket.close();
}
}
}
}