/*******************************************************************************
* Copyright (c) 2010 Denis Solonenko.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v2.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* Denis Solonenko - initial API and implementation
******************************************************************************/
package ru.orangesoftware.orb;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityNotFoundException;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.PersistenceException;
import javax.persistence.Table;
import javax.persistence.Transient;
import ru.orangesoftware.financisto2.db.DatabaseHelper;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public abstract class EntityManager {
private static final ConcurrentMap<Class<?>, EntityDefinition> definitions = new ConcurrentHashMap<Class<?>, EntityDefinition>();
@Bean
public DatabaseHelper dbHelper;
public SQLiteDatabase db() {
return dbHelper.getWritableDatabase();
}
private static EntityDefinition parseDefinition(Class<?> clazz) {
if (!clazz.isAnnotationPresent(Entity.class)) {
throw new IllegalArgumentException("Class "+clazz+" is not an @Entity");
}
EntityDefinition.Builder edb = new EntityDefinition.Builder(clazz);
try {
Constructor<?> constructor = clazz.getConstructor();
edb.withConstructor(constructor);
} catch (Exception e) {
throw new IllegalArgumentException("Entity must have an empty constructor");
}
if (clazz.isAnnotationPresent(Table.class)) {
Table tableAnnotation = clazz.getAnnotation(Table.class);
edb.withTable(tableAnnotation.name());
}
Field[] fields = clazz.getFields();
if (fields != null) {
int index = 0;
for (Field f : fields) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
if (f.isAnnotationPresent(Id.class)) {
edb.withIdField(parseField(f));
} else {
if (f.isAnnotationPresent(Transient.class)) {
continue;
} else if (f.isAnnotationPresent(JoinColumn.class)) {
JoinColumn c = f.getAnnotation(JoinColumn.class);
edb.withField(FieldInfo.entity(index++, f, c.name(), c.required()));
} else {
edb.withField(parseField(f));
}
}
}
}
}
return edb.create();
}
private static FieldInfo parseField(Field f) {
String columnName;
if (f.isAnnotationPresent(Column.class)) {
columnName = f.getAnnotation(Column.class).name();
} else {
columnName = f.getName().toUpperCase();
}
return FieldInfo.primitive(f, columnName);
}
static EntityDefinition getEntityDefinitionOrThrow(Class<?> clazz) {
EntityDefinition ed = definitions.get(clazz);
if (ed == null) {
EntityDefinition ned = parseDefinition(clazz);
ed = definitions.putIfAbsent(clazz, ned);
if (ed == null) {
ed = ned;
}
}
return ed;
}
public long saveOrUpdate(Object entity) {
if (entity == null) {
throw new IllegalArgumentException("Entity is null");
}
SQLiteDatabase db = db();
EntityDefinition ed = getEntityDefinitionOrThrow(entity.getClass());
ContentValues values = getContentValues(ed, entity);
long id = ed.getId(entity);
values.remove("updated_on");
values.put("updated_on", System.currentTimeMillis());
if (id <= 0) {
values.remove(ed.idField.columnName);
id = db.insertOrThrow(ed.tableName, null, values);
ed.setId(entity, id);
return id;
} else {
values.remove("updated_on");
values.put("updated_on", System.currentTimeMillis());
db.update(ed.tableName, values, ed.idField.columnName+"=?", new String[]{String.valueOf(id)});
return id;
}
}
public long reInsert(Object entity) {
if (entity == null) {
throw new IllegalArgumentException("Entity is null");
}
SQLiteDatabase db = db();
EntityDefinition ed = getEntityDefinitionOrThrow(entity.getClass());
ContentValues values = getContentValues(ed, entity);
long id = ed.getId(entity);
long newId = db.insertOrThrow(ed.tableName, null, values);
if (id != newId) {
throw new IllegalArgumentException("Unable to re-insert "+entity.getClass()+" with id "+id);
}
return id;
}
private ContentValues getContentValues(EntityDefinition ed, Object entity) {
ContentValues values = new ContentValues();
FieldInfo[] fields = ed.fields;
for (FieldInfo fi : fields) {
try {
if (fi.type.isPrimitive()) {
Object value = fi.field.get(entity);
fi.type.setValue(values, fi.columnName, value);
} else {
Object e = fi.field.get(entity);
if (e == null) {
values.putNull(fi.columnName);
} else {
EntityDefinition eed = getEntityDefinitionOrThrow(e.getClass());
FieldInfo ffi = eed.idField;
Object value = ffi.field.get(e);
ffi.type.setValue(values, fi.columnName, value);
}
}
} catch (Exception e) {
throw new PersistenceException("Unable to create content values for "+entity, e);
}
}
return values;
}
public <T> T load(Class<T> clazz, Object id) {
T e = get(clazz, id);
if (e != null) {
return e;
} else {
throw new EntityNotFoundException(clazz, id);
}
}
public <T> T get(Class<T> clazz, Object id) {
if (id == null) {
throw new IllegalArgumentException("Id can't be null");
}
EntityDefinition ed = getEntityDefinitionOrThrow(clazz);
StringBuilder sb = new StringBuilder(ed.sqlQuery);
sb.append(" where e_").append(ed.idField.columnName).append("=?");
String sql = sb.toString();
Cursor c = db().rawQuery(sql, new String[]{id.toString()});
try {
if (c.moveToFirst()) {
try {
return (T)loadFromCursor("e", c, ed);
} catch (Exception e) {
throw new PersistenceException("Unable to load entity of type "+clazz+" with id "+id, e);
}
}
} finally {
c.close();
}
return null;
}
public <T> List<T> list(Class<T> clazz) {
EntityDefinition ed = getEntityDefinitionOrThrow(clazz);
Cursor c = db().rawQuery(ed.sqlQuery, null);
try {
List<T> list = new LinkedList<T>();
while (c.moveToNext()) {
try {
T t = (T)loadFromCursor("e", c, ed);
list.add(t);
} catch (Exception e) {
throw new PersistenceException("Unable to list entites of type "+clazz, e);
}
}
return list;
} finally {
c.close();
}
}
public static <T> T loadFromCursor(Cursor c, Class<T> clazz) {
EntityDefinition ed = getEntityDefinitionOrThrow(clazz);
try {
return (T)loadFromCursor("e", c, ed);
} catch (Exception e) {
throw new PersistenceException("Unable to load entity of type "+clazz+" from cursor", e);
}
}
private static <T> T loadFromCursor(String pe, Cursor c, EntityDefinition ed) throws Exception {
int idIndex = c.getColumnIndexOrThrow(pe+"__id");
if (c.isNull(idIndex)) {
return null;
}
@SuppressWarnings("unchecked")
T entity = (T)ed.constructor.newInstance();
FieldInfo[] fields = ed.fields;
int count = fields.length;
for (int i = 0; i<count; i++) {
FieldInfo fi = fields[i];
Object value;
if (fi.type.isPrimitive()) {
value = fi.type.getValueFromCursor(c, pe+"_"+fi.columnName);
} else {
EntityDefinition eed = getEntityDefinitionOrThrow(fi.field.getType());
value = loadFromCursor(pe+fi.index, c, eed);
}
fi.field.set(entity, value);
}
return entity;
}
public <T> int delete(Class<T> clazz, Object id) {
if (id == null) {
throw new IllegalArgumentException("Id can't be null");
}
EntityDefinition ed = getEntityDefinitionOrThrow(clazz);
for (FieldInfo fI: ed.fields) {
if (fI.columnName=="remote_key") {
Cursor cursor = db().query(ed.tableName,
new String[]{"remote_key"}, ed.idField.columnName + "=?",
new String[]{String.valueOf(id)}, null, null, null, null);
if (cursor != null) {
cursor.moveToFirst();
ContentValues row = new ContentValues();
row.put(DatabaseHelper.deleteLogColumns.TABLE_NAME, ed.tableName);
row.put(DatabaseHelper.deleteLogColumns.REMOTE_KEY,cursor.getString(0));
row.put(DatabaseHelper.deleteLogColumns.DELETED_ON, System.currentTimeMillis());
db().insert(DatabaseHelper.DELETE_LOG_TABLE, null, row);
Log.e("financisto","stored deleteLog " + ed.tableName + " "+ cursor.getString(0));
}
}
}
return db().delete(ed.tableName, ed.idField.columnName + "=?", new String[]{id.toString()});
}
public <T> Query<T> createQuery(Class<T> clazz) {
return new Query<T>(this, clazz);
}
}