package com.alvazan.orm.impl.meta.data; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import javassist.util.proxy.Proxy; import com.alvazan.orm.api.base.spi.KeyGenerator; import com.alvazan.orm.api.z5api.NoSqlSession; import com.alvazan.orm.api.z8spi.Row; import com.alvazan.orm.api.z8spi.conv.Converter; import com.alvazan.orm.api.z8spi.conv.StorageTypeEnum; import com.alvazan.orm.api.z8spi.meta.DboColumnIdMeta; import com.alvazan.orm.api.z8spi.meta.DboColumnMeta; import com.alvazan.orm.api.z8spi.meta.DboTableMeta; import com.alvazan.orm.api.z8spi.meta.IndexData; import com.alvazan.orm.api.z8spi.meta.InfoForIndex; import com.alvazan.orm.api.z8spi.meta.ReflectionUtil; import com.alvazan.orm.api.z8spi.meta.RowToPersist; import com.alvazan.orm.impl.meta.data.collections.CacheLoadCallback; import com.eaio.uuid.UUID; //NOTE: T is the entity type NOT the type of the id!!! public class MetaIdField<OWNER> extends MetaAbstractField<OWNER> { private Converter converter; private boolean useGenerator; private KeyGenerator generator; private Method method; private MetaAbstractClass<OWNER> metaClass; private DboColumnIdMeta metaDbo = new DboColumnIdMeta(); public DboColumnMeta getMetaDbo() { return metaDbo; } @Override public String toString() { return "MetaField [field='" + field.getDeclaringClass().getName()+"."+field.getName()+"(field type=" +field.getType()+ ")"; } public void translateFromColumn(Row row, OWNER entity, NoSqlSession session) { byte[] virtKey = row.getKey(); byte[] nonVirtKey = metaDbo.unformVirtRowKey(virtKey); Object entityId = converter.convertFromNoSql(nonVirtKey); ReflectionUtil.putFieldValue(entity, field, entityId); } @Override public void translateToColumn(InfoForIndex<OWNER> info) { OWNER entity = info.getEntity(); RowToPersist row = info.getRow(); Object id = fillInAndFetchId(entity); byte[] byteVal = converter.convertToNoSql(id); byte[] virtualKey = metaDbo.formVirtRowKey(byteVal); row.setKeys(byteVal, virtualKey); StorageTypeEnum storageType = metaDbo.getStorageType(); addIndexInfo(info, id, byteVal, storageType); //NOTICE: there is no call to remove because if an id is changed, it is a new entity and we only need to add index not remove since //the old entity was not deleted during this save....we remove from index on remove of old entity only } @Override public Object fetchField(Object entity) { throw new UnsupportedOperationException("only used for partitioning and id can't partition. easy to implement if anyone else starts using this though, but for now unsupported"); } @Override public String translateToString(Object fieldsValue) { throw new UnsupportedOperationException("only used for partitioning and id can't partition. easy to implement if anyone else starts using this though, but for now unsupported"); } @Override public void removingEntity(InfoForIndex<OWNER> info, List<IndexData> indexRemoves, byte[] pk) { if (metaDbo.isIndexed()) { StorageTypeEnum storageType = getMetaDbo().getStorageType(); addToList(info, pk, storageType, pk, indexRemoves); } } public Object fillInAndFetchId(OWNER entity) { Object idInEntity = ReflectionUtil.fetchFieldValue(entity, field); Object id = idInEntity; if(!useGenerator) { if(id == null) throw new IllegalArgumentException("Entity has @NoSqlEntity(usegenerator=false) but this entity has no id="+entity); return id; } else if(id != null) { //to make it easier on users, we now check and read in from database if they have not already //OKAY, we definitely do NOT want them setting their own id when autogeneration is used as IF you set the id on TWO entities //to the same id and add both of them, you end up with a corrupt index in that you have duplicates of two values pointing to //the same exact primary key. // if(!(entity instanceof NoSqlProxy)) // throw new IllegalArgumentException("Uhm, uh, you have useGenerator=true(the default) on @NoSqlId annotation and the entity you " + // "passed in was NOT read from the database!!!! You supplied " + // "your own id which could exist in the database...this will cause LARGE issues with indexing so we don't allow it, please don't set the id OR you" + // " are using a primitive for your key which is not a good idea either if you are going to use a generator(use Integer or String or Long instead)"); return id; } Object newId; if (field.getType().equals(UUID.class)) newId = new UUID(); else newId = generator.generateNewKey(entity); ReflectionUtil.putFieldValue(entity, field, newId); return newId; } @SuppressWarnings("unchecked") public void setup(DboTableMeta tableMeta, IdInfo info, Field field, String columnName, boolean isIndexed) { this.method = info.getIdMethod(); this.method.setAccessible(true); this.useGenerator = info.isUseGenerator(); this.generator = info.getGen(); this.converter = info.getConverter(); this.metaClass = info.getMetaClass(); metaDbo.setup(tableMeta, columnName, field.getType(), isIndexed); super.setup(field, columnName); } public Converter getConverter() { return converter; } public Method getIdMethod() { return method; } public OWNER convertIdToProxy(NoSqlSession session, Object entityId, CacheLoadCallback cacheLoadCallback, Class<?> clazz) { if(entityId == null) return null; OWNER proxy = createProxy(entityId, session, cacheLoadCallback, clazz); ReflectionUtil.putFieldValue(proxy, field, entityId); return proxy; } @SuppressWarnings("unchecked") private OWNER createProxy(Object entityId, NoSqlSession session, CacheLoadCallback cacheLoadCallback, Class<?> clazz) { Class<?> subclassProxyClass = metaClass.getProxyClass(clazz); Proxy inst = (Proxy) ReflectionUtil.create(subclassProxyClass); inst.setHandler(new NoSqlProxyImpl<OWNER>(session, metaClass, entityId, cacheLoadCallback)); return (OWNER) inst; } @Override protected Object unwrapIfNeeded(Object value) { return value; // no need to unwrap keys } @Override public byte[] translateValue(Object value) { return converter.convertToNoSql(value); } public DboColumnIdMeta getMetaIdDbo() { return metaDbo; } public Object translateFromBytes(byte[] val) { return converter.convertFromNoSql(val); } public boolean isAutoGen() { return useGenerator; } public byte[] convertIdToNonVirtKey(Object pk) { return converter.convertToNoSql(pk); } public byte[] formVirtRowKey(byte[] rowKey) { byte[] virtKey = metaDbo.formVirtRowKey(rowKey); return virtKey; } public byte[] unformVirtRowKey(byte[] virtualKey) { return metaDbo.unformVirtRowKey(virtualKey); } }