package com.alvazan.orm.impl.meta.data;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.util.proxy.MethodHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alvazan.orm.api.exc.RowNotFoundException;
import com.alvazan.orm.api.z5api.NoSqlSession;
import com.alvazan.orm.api.z8spi.KeyValue;
import com.alvazan.orm.api.z8spi.Row;
import com.alvazan.orm.api.z8spi.conv.Converter;
import com.alvazan.orm.api.z8spi.iter.AbstractCursor;
import com.alvazan.orm.api.z8spi.iter.AbstractCursor.Holder;
import com.alvazan.orm.api.z8spi.iter.IterableWrappingCursor;
import com.alvazan.orm.api.z8spi.meta.DboColumnIdMeta;
import com.alvazan.orm.api.z8spi.meta.DboTableMeta;
import com.alvazan.orm.impl.meta.data.collections.CacheLoadCallback;
public class NoSqlProxyImpl<T> implements MethodHandler {
private static final Logger log = LoggerFactory.getLogger(NoSqlProxyImpl.class);
private NoSqlSession session;
private Object entityId;
private Method idMethod;
private MetaAbstractClass<T> classMeta;
private boolean isInitialized = false;
private CacheLoadCallback cacheLoadCallback;
private Map<Field, Object> indexFieldToOriginalValue = new HashMap<Field, Object>();
public NoSqlProxyImpl(NoSqlSession session, MetaAbstractClass<T> classMeta, Object entityId, CacheLoadCallback cacheLoadCallback) {
if(classMeta.getColumnFamily() == null)
throw new IllegalArgumentException("column family in the classMeta parameter cannot be null");
if(session == null && cacheLoadCallback == null)
throw new IllegalArgumentException("Need session or cacheCallback");
else if(session != null && cacheLoadCallback != null)
throw new IllegalArgumentException("You must supply a cacheLoadCallback OR a session but NOT both...give us session and we load it, give us cacheLoad and we tell you to load your list of proxies when needed");
this.session = session;
this.entityId = entityId;
this.classMeta = classMeta;
this.idMethod = classMeta.getIdField().getIdMethod();
this.cacheLoadCallback = cacheLoadCallback;
}
/**
* @param selfArg - The proxy object(if you call any method on self, it will result in calling invoke method
* AND that includes a simple toString like if you did log.info("proxy="+self);
* @param superClassMethod - The method that is on the superclass like Account.java
* @param subclassProxyMethod - The method that is on the proxy like Account_$$_javassist_0
*/
@SuppressWarnings("unchecked")
@Override
public Object invoke(Object selfArg, Method superClassMethod, Method subclassProxyMethod, Object[] args)
throws Throwable {
T self = (T)selfArg;
if(log.isTraceEnabled()) {
log.trace("name="+superClassMethod.getName()+" superClass type="+superClassMethod.getDeclaringClass());
log.trace("name="+subclassProxyMethod.getName()+" proxy type="+subclassProxyMethod.getDeclaringClass());
}
//Here we shortcut as we do not need to go to the database...
if(idMethod.equals(superClassMethod))
return entityId;
else if("__markInitializedAndCacheIndexedValues".equals(superClassMethod.getName())) {
cacheIndexedValues(self);
isInitialized = true;
return null;
} else if("__getOriginalValues".equals(superClassMethod.getName())) {
return getOriginalValues();
}
//Any other method that is called, toString, getHashCode, getName, someMethod() all end up
//loading the objects fields from the database in case those methods use those fields
if(!isInitialized) {
//If we have a cacheLoadCallback from a List or Map, we are not just loading
//this entity but the callback method will load this and all other entities from
//the database in ONE single call instead.
if(cacheLoadCallback != null) {
cacheLoadCallback.loadCacheIfNeeded();
} else {
fillInThisOneInstance(self);
}
isInitialized = true;
}
//Not sure if this should be subclassProxyMethod or superClassMethod
return subclassProxyMethod.invoke(self, args); // execute the original method.
}
private Map<Field, Object> getOriginalValues() {
return indexFieldToOriginalValue;
}
private void cacheIndexedValues(T self) {
List<MetaField<T>> cols = classMeta.getIndexedColumns();
for(MetaField<T> f : cols) {
Field field = f.getField();
Object value = f.getFieldRawValue(self);
indexFieldToOriginalValue.put(field, value);
}
}
private void fillInThisOneInstance(T self) {
MetaIdField<T> idField = classMeta.getIdField();
Converter converter = idField.getConverter();
byte[] nonVirtKey = converter.convertToNoSql(entityId);
DboTableMeta metaDbo = classMeta.getMetaDbo();
DboColumnIdMeta idMeta = metaDbo.getIdColumnMeta();
byte[] virtKey = idMeta.formVirtRowKey(nonVirtKey);
List<byte[]> rowKeys = new ArrayList<byte[]>();
rowKeys.add(virtKey);
AbstractCursor<KeyValue<Row>> rows = session.find(metaDbo, new IterableWrappingCursor<byte[]>(rowKeys), false, true, null);
Holder<KeyValue<Row>> holder = rows.nextImpl();
if(holder == null)
throw new RowNotFoundException("row for type="+classMeta.getMetaClass().getName()+" not found for key="+entityId);
KeyValue<Row> next = holder.getValue();
if(next.getValue() == null)
throw new RowNotFoundException("row for type="+classMeta.getMetaClass().getName()+" not found for key="+entityId);
Row row = next.getValue();
classMeta.fillInInstance(row, session, self);
}
}