/**
*
*/
package siena.gae;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import siena.ClassInfo;
import siena.Json;
import siena.SienaException;
import siena.Util;
import siena.core.DecimalPrecision;
import siena.embed.Embedded;
import siena.embed.JavaSerializer;
import siena.embed.JsonSerializer;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Text;
/**
* @author mandubian <pascal.voitot@mandubian.org>
*
*/
public class GaeNativeSerializer {
public static void embed(Entity entity, String embeddingColumnName, Object embeddedObj, int level){
// the level prevents from stackoverflow in case of a circular ref
if(level > 2) return;
Class<?> clazz = embeddedObj.getClass();
if(clazz.isArray() || Collection.class.isAssignableFrom(clazz)){
throw new SienaException("can't serializer Array/Collection in native mode");
}
for (Field f : ClassInfo.getClassInfo(clazz).allFields) {
// doesn't try to analyze fields, just try to store it
Class<?> fieldClass = f.getType();
String propName = embeddingColumnName + "." + ClassInfo.getSingleColumnName(f);
Object propValue = Util.readField(embeddedObj, f);
if (propValue != null) {
if (fieldClass == Json.class) {
propValue = propValue.toString();
} else if (propValue instanceof String) {
String s = (String) propValue;
if (s.length() > 500)
propValue = new Text(s);
} else if (propValue instanceof byte[]) {
byte[] arr = (byte[]) propValue;
// GAE Blob doesn't accept more than 1MB
if (arr.length < 1000000)
propValue = new Blob(arr);
else
propValue = new Blob(Arrays.copyOf(arr, 1000000));
}
else if (ClassInfo.isEmbedded(f)) {
Embedded embed = f.getAnnotation(Embedded.class);
switch(embed.mode()){
case SERIALIZE_JSON:
propValue = JsonSerializer.serialize(propValue).toString();
String s = (String) propValue;
if (s.length() > 500)
propValue = new Text(s);
break;
case SERIALIZE_JAVA:
// this embedding mode doesn't manage @EmbedIgnores
try {
byte[] b = JavaSerializer.serialize(propValue);
// if length is less than 1Mb, can store in a blob else???
if(b.length <= 1000000){
propValue = new Blob(b);
}else{
throw new SienaException("object can be java serialized because it's too large >1mb");
}
}
catch(IOException ex) {
throw new SienaException(ex);
}
break;
case NATIVE:
GaeNativeSerializer.embed(entity, embeddingColumnName + "." + ClassInfo.getSingleColumnName(f), propValue, level+1);
// has set several new properties in entity so go to next field
continue;
}
}
else if (fieldClass == BigDecimal.class){
DecimalPrecision ann = f.getAnnotation(DecimalPrecision.class);
if(ann == null) {
propValue = ((BigDecimal)propValue).toPlainString();
}else {
switch(ann.storageType()){
case DOUBLE:
propValue = ((BigDecimal)propValue).doubleValue();
break;
case STRING:
case NATIVE:
propValue = ((BigDecimal)propValue).toPlainString();
break;
}
}
}
// enum is after embedded because an enum can be embedded
// don't know if anyone will use it but it will work :)
else if (Enum.class.isAssignableFrom(fieldClass)) {
propValue = propValue.toString();
}
else if(ClassInfo.isModel(fieldClass)){
// if it's a model and as there a no join, we can't fetch everything! So only the key is embedded
//GaeNativeSerializer.embed(entity, embeddingColumnName + "." + ClassInfo.getSingleColumnName(f), propValue, level + 1);
propValue = Util.readField(propValue, ClassInfo.getIdField(fieldClass));
}
}
Unindexed ui = f.getAnnotation(Unindexed.class);
if (ui == null) {
entity.setProperty(propName, propValue);
} else {
entity.setUnindexedProperty(propName, propValue);
}
}
}
public static <T> T unembed(Class<T> clazz, String embeddingFieldName, Entity entity, int level){
// the level prevents from stackoverflow in case of a circular ref
if(level > 2) return null;
if(clazz.isArray() || Collection.class.isAssignableFrom(clazz)){
throw new SienaException("can't serializer Array/Collection in native mode");
}
T obj = Util.createObjectInstance(clazz);
try {
for (Field f : ClassInfo.getClassInfo(clazz).allFields) {
// doesn't try to analyze fields, just try to store it
String propName = embeddingFieldName + "." + ClassInfo.getSingleColumnName(f);
Object propValue = entity.getProperty(propName);
if(ClassInfo.isEmbedded(f) && f.getAnnotation(Embedded.class).mode() == Embedded.Mode.NATIVE){
Object value = GaeNativeSerializer.unembed(
f.getType(), embeddingFieldName + "." + ClassInfo.getSingleColumnName(f), entity, level+1);
Util.setField(obj, f, value);
}
else if(ClassInfo.isModel(f.getType())){
// here we create a model with only the key as we don't embed anything else because there is no join by default
Class<?> fieldClass = f.getType();
Object value = Util.createObjectInstance(fieldClass);
Util.setField(value, ClassInfo.getIdField(fieldClass), propValue);
Util.setField(obj, f, value);
}
else {
GaeMappingUtils.setFromObject(obj, f, propValue);
}
}
return obj;
}catch(Exception e){
throw new SienaException(e);
}
}
}