package siena.jdbc;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import siena.ClassInfo;
import siena.Json;
import siena.Query;
import siena.QueryJoin;
import siena.SienaException;
import siena.Util;
import siena.core.DecimalPrecision;
import siena.core.Polymorphic;
import siena.embed.Embedded;
import siena.embed.JsonSerializer;
import siena.jdbc.JdbcPersistenceManager.JdbcClassInfo;
public class JdbcMappingUtils {
public static <T> List<Field> getJoinFields(Query<T> query) {
List<Field> joinFields = null;
// adds all join fields brought by call to .join() functions
if(query.getJoins().size()>0){
joinFields = new ArrayList<Field>();
for(QueryJoin join:query.getJoins())
joinFields.add(join.field);
}
// then adds the remaining joins coming from @Join if not added yet
ClassInfo ci = ClassInfo.getClassInfo(query.getQueriedClass());
if(ci.joinFields.size() > 0){
if(joinFields == null) joinFields = new ArrayList<Field>();
for(Field f: ci.joinFields){
if(!joinFields.contains(f)) joinFields.add(f);
}
}
return joinFields;
}
public static <T> List<Field> getJoinFields(Query<T> query, JdbcClassInfo info) {
List<Field> joinFields = null;
// adds all join fields brought by call to .join() functions
if(query.getJoins()!=null && query.getJoins().size()>0){
joinFields = new ArrayList<Field>();
for(QueryJoin join:query.getJoins())
joinFields.add(join.field);
}
// then adds the remaining joins coming from @Join if not added yet
if(info.joinFields!=null && info.joinFields.size() > 0){
if(joinFields == null) joinFields = new ArrayList<Field>();
for(Field f: info.joinFields){
if(!joinFields.contains(f)) joinFields.add(f);
}
}
return joinFields;
}
public static <T> T mapObject(Class<T> clazz, ResultSet rs, String tableName, List<Field >joinFields) {
try {
T obj = Util.createObjectInstance(clazz);
mapObject(obj, rs, tableName, joinFields);
return obj;
} catch(SienaException e) {
throw e;
} catch(Exception e) {
throw new SienaException(e);
}
}
public static void mapObject(Object obj, ResultSet rs, String tableName, List<Field >joinFields) {
Class<?> clazz = obj.getClass();
for (Field field : JdbcClassInfo.getClassInfo(clazz).allFields) {
mapField(obj, field, rs, tableName, joinFields);
}
}
public static <T> List<T> mapList(Class<T> clazz, ResultSet rs, String tableName, List<Field> joinFields, int pageSize) {
try {
List<T> objects = new ArrayList<T>();
if(pageSize==0){
while(rs.next()) {
objects.add(mapObject(clazz, rs, tableName, joinFields));
}
}else {
for(int i=0; i<pageSize && rs.next();i++){
objects.add(mapObject(clazz, rs, tableName, joinFields));
}
}
return objects;
} catch(SienaException e) {
throw e;
} catch(Exception e) {
throw new SienaException(e);
}
}
public static <T> T mapObjectKeys(Class<T> clazz, ResultSet rs, String tableName, List<Field> joinFields) {
try {
T obj = Util.createObjectInstance(clazz);
mapObjectKeys(obj, rs, tableName, joinFields);
return obj;
} catch(SienaException e) {
throw e;
} catch(Exception e) {
throw new SienaException(e);
}
}
public static void mapObjectKeys(Object obj, ResultSet rs, String tableName, List<Field> joinFields) {
Class<?> clazz = obj.getClass();
for (Field field : JdbcClassInfo.getClassInfo(clazz).keys) {
mapField(obj, field, rs, tableName, joinFields);
}
}
public static <T> List<T> mapListKeys(Class<T> clazz, ResultSet rs, String tableName, List<Field> joinFields, int pageSize) {
try {
List<T> objects = new ArrayList<T>();
if(pageSize==0){
while(rs.next()) {
objects.add(mapObjectKeys(clazz, rs, tableName, joinFields));
}
}else {
for(int i=0; i<pageSize && rs.next();i++){
objects.add(mapObjectKeys(clazz, rs, tableName, joinFields));
}
}
return objects;
} catch(SienaException e) {
throw e;
} catch(Exception e) {
throw new SienaException(e);
}
}
public static void mapField(Object obj, Field field, ResultSet rs, String tableName, List<Field> joinFields) {
Class<?> type = field.getType();
//field.setAccessible(true);
try {
if(ClassInfo.isModel(type) && !ClassInfo.isEmbedded(field)) {
JdbcClassInfo fieldClassInfo = JdbcClassInfo.getClassInfo(type);
if(joinFields==null || joinFields.size()==0 || !joinFields.contains(field)){
String[] fks = ClassInfo.getColumnNames(field, tableName);
Object rel = Util.createObjectInstance(type);
boolean none = false;
int i = 0;
checkForeignKeyMapping(fieldClassInfo.keys, fks, obj.getClass(), field);
for(Field f : fieldClassInfo.keys) {
Object o = rs.getObject(JdbcClassInfo.aliasFromCol(fks[i++]));
if(o == null) {
none = true;
break;
}
setFromObject(rel, f, o);
}
if(!none){
Util.setField(obj, field, rel);
//field.set(obj, rel);
}
}
else {
// this is a JOIN field
// first verifies the field is not null
Object val = rs.getObject(
JdbcClassInfo.aliasFromCol(
ClassInfo.getColumnNames(field, tableName)[0]));
if(val == null){
Util.setField(obj, field, null);
return;
}
// uses join field alias
// Object rel = mapObject(type, rs, fieldClassInfo.tableName, null);
Object rel = mapObject(type, rs, fieldClassInfo.joinFieldAliases.get(field.getName()), null);
Util.setField(obj, field, rel);
}
} else {
Object val = rs.getObject(ClassInfo.getColumnNames(field, tableName)[0].replace('.', '_'));
setFromObject(obj, field, val);
}
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
}
}
public static void checkForeignKeyMapping(List<Field> keys, String[] columns, Class<?> clazz, Field field) {
if (keys.size() != columns.length) {
throw new SienaException("Bad mapping for field '"+field.getName()+"'. " +
"Related class "+field.getType().getName()+" has "+keys.size()+" primary keys, " +
"but '"+clazz.getName()+"' only has mappings for "+columns.length+" foreign keys");
}
}
public static void setFromObject(Object object, Field f, Object value)
throws IllegalArgumentException, IllegalAccessException {
Util.setField(object, f, fromObject(f, value));
}
public static Object fromObject(Field field, Object value) {
Class<?> type = field.getType();
// in H2 database, mediumtext is mapped to CLOB
if(Json.class.isAssignableFrom(type) && value != null && java.sql.Clob.class.isAssignableFrom(value.getClass())) {
java.sql.Clob clob = (java.sql.Clob)value;
try {
return Json.load(new BufferedReader(clob.getCharacterStream()));
} catch (SQLException e) {
throw new SienaException(e);
}
}
if(field.getAnnotation(Embedded.class) != null && value != null && java.sql.Clob.class.isAssignableFrom(value.getClass())) {
java.sql.Clob clob = (java.sql.Clob)value;
try {
Json data = Json.load(new BufferedReader(clob.getCharacterStream()));
return JsonSerializer.deserialize(field, data);
} catch (SQLException e) {
throw new SienaException(e);
}
}
// issue https://github.com/mandubian/siena/issues/5
if (value != null && java.sql.Clob.class.isAssignableFrom(value.getClass())) {
java.sql.Clob clob = (java.sql.Clob) value;
try {
// @see http://osdir.com/ml/h2-database/2011-06/msg00170.html
return clob.getSubString(1, (int) clob.length());
} catch (SQLException e) {
throw new SienaException(e);
}
}
if(field.isAnnotationPresent(Polymorphic.class)){
try {
if(java.sql.Blob.class.isAssignableFrom(value.getClass())){
java.sql.Blob blob = (java.sql.Blob)value;
ObjectInputStream in =
new ObjectInputStream(new ByteArrayInputStream(blob.getBytes(0, (int)blob.length())));
return in.readObject();
}else {
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream((byte[])value));
return in.readObject();
}
} catch (IOException e) {
throw new SienaException(e);
} catch (ClassNotFoundException e) {
throw new SienaException(e);
} catch(SQLException e){
throw new SienaException(e);
}
}
if(byte[].class == type && value != null && java.sql.Blob.class.isAssignableFrom(value.getClass())){
java.sql.Blob blob = (java.sql.Blob)value;
try {
// converts the blob into a byte[]...
// TODO what to do with a very long blob????
return blob.getBytes(0, (int)blob.length());
} catch (SQLException e) {
throw new SienaException(e);
}
}
if(BigDecimal.class == type){
DecimalPrecision ann = field.getAnnotation(DecimalPrecision.class);
if(ann==null){
return (BigDecimal)value;
}else {
switch(ann.storageType()){
case DOUBLE:
return BigDecimal.valueOf((Double)value);
case STRING:
return new BigDecimal((String)value);
case NATIVE:
return (BigDecimal)value;
}
}
}
return Util.fromObject(field, value);
}
}