package io.shockah.skylark.db;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.field.DataPersisterManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.DatabaseTable;
import com.j256.ormlite.table.TableUtils;
import io.shockah.json.JSONObject;
import io.shockah.json.JSONParser;
import io.shockah.json.JSONPrettyPrinter;
import io.shockah.skylark.App;
import io.shockah.skylark.UnexpectedException;
import io.shockah.skylark.func.Action1;
public class DatabaseManager implements Closeable {
public static final Path TABLE_VERSIONS_PATH = Paths.get("tableVersions.json");
public final App app;
protected final ConnectionSource connection;
private final Object lock = new Object();
private final List<Class<?>> createdTables = new ArrayList<>();
private final JSONObject tableVersions;
public DatabaseManager(App app) {
this.app = app;
try {
connection = new JdbcConnectionSource("jdbc:" + app.config.getObject("database").getString("databasePath"));
DataPersisterManager.registerDataPersisters(new PatternPersister());
DataPersisterManager.registerDataPersisters(new JSONObjectPersister());
if (Files.exists(TABLE_VERSIONS_PATH))
tableVersions = new JSONParser().parseObject(new String(Files.readAllBytes(TABLE_VERSIONS_PATH), "UTF-8"));
else
tableVersions = new JSONObject();
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> Dao<T, Integer> getDao(Class<T> clazz) {
return getDao(clazz, Integer.class);
}
public <T extends DbObject<T>, ID> Dao<T, ID> getDao(Class<T> clazz, Class<ID> clazzId) {
try {
synchronized (lock) {
Dao<T, ID> dao = DaoManager.lookupDao(connection, clazz);
if (dao == null)
dao = DaoManager.createDao(connection, clazz);
createTableIfNeeded(dao, clazz);
return dao;
}
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
private <T extends DbObject<T>, ID> void createTableIfNeeded(Dao<T, ID> dao, Class<T> clazz) {
synchronized (lock) {
if (createdTables.contains(clazz))
return;
try {
TableUtils.createTableIfNotExists(connection, clazz);
String databaseTable = getDatabaseTable(clazz);
int tableVersion = getTableVersion(clazz);
int oldTableVersion = tableVersions.getInt(databaseTable, 0);
if (tableVersion > oldTableVersion) {
if (oldTableVersion != 0) {
try {
Method method = clazz.getMethod("migrate", Dao.class, int.class, int.class);
if (Modifier.isStatic(method.getModifiers())) {
method.invoke(null, dao, oldTableVersion, tableVersion);
}
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
tableVersions.put(databaseTable, tableVersion);
saveTableVersions();
}
} catch (SQLException e) {
throw new UnexpectedException(e);
}
createdTables.add(clazz);
}
}
private <T extends DbObject<T>> String getDatabaseTable(Class<T> clazz) {
DatabaseTable databaseTable = clazz.getAnnotation(DatabaseTable.class);
return databaseTable == null ? clazz.getSimpleName().toLowerCase() : databaseTable.tableName();
}
private <T extends DbObject<T>> int getTableVersion(Class<T> clazz) {
DbObject.TableVersion tableVersion = clazz.getAnnotation(DbObject.TableVersion.class);
return tableVersion == null ? 1 : tableVersion.value();
}
private void saveTableVersions() {
try {
Files.write(TABLE_VERSIONS_PATH, new JSONPrettyPrinter().toString(tableVersions).getBytes("UTF-8"));
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> T make(Class<T> clazz) {
try {
T obj = clazz.getConstructor(Dao.class).newInstance(getDao(clazz, Integer.class));
obj.manager = new WeakReference<>(this);
return obj;
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> T create(Class<T> clazz, Action1<T> f) {
try {
T obj = make(clazz);
f.call(obj);
obj.create();
return obj;
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> T get(Class<T> clazz, int id) {
try {
T obj = getDao(clazz).queryForId(id);
if (obj != null)
obj.manager = new WeakReference<>(this);
return obj;
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> List<T> query(Class<T> clazz, SQLExceptionWrappedAction1<QueryBuilder<T, Integer>> f) {
try {
QueryBuilder<T, Integer> builder = getDao(clazz).queryBuilder();
f.call(builder);
List<T> objs = builder.query();
for (T obj : objs) {
obj.manager = new WeakReference<>(this);
}
return objs;
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> List<T> query(Class<T> clazz, SQLExceptionWrappedAction2<QueryBuilder<T, Integer>, WhereBuilder> f) {
try {
QueryBuilder<T, Integer> builder = getDao(clazz).queryBuilder();
f.call(builder, new WhereBuilder(builder.where()));
List<T> objs = builder.query();
for (T obj : objs) {
obj.manager = new WeakReference<>(this);
}
return objs;
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> T queryFirst(Class<T> clazz, SQLExceptionWrappedAction1<QueryBuilder<T, Integer>> f) {
try {
QueryBuilder<T, Integer> builder = getDao(clazz).queryBuilder();
f.call(builder);
T obj = builder.queryForFirst();
if (obj != null)
obj.manager = new WeakReference<>(this);
return obj;
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> T queryFirst(Class<T> clazz, SQLExceptionWrappedAction2<QueryBuilder<T, Integer>, WhereBuilder> f) {
try {
QueryBuilder<T, Integer> builder = getDao(clazz).queryBuilder();
f.call(builder, new WhereBuilder(builder.where()));
T obj = builder.queryForFirst();
if (obj != null)
obj.manager = new WeakReference<>(this);
return obj;
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> int delete(Class<T> clazz, SQLExceptionWrappedAction1<DeleteBuilder<T, Integer>> f) {
try {
DeleteBuilder<T, Integer> builder = getDao(clazz).deleteBuilder();
f.call(builder);
return builder.delete();
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
public <T extends DbObject<T>> int delete(Class<T> clazz, SQLExceptionWrappedAction2<DeleteBuilder<T, Integer>, WhereBuilder> f) {
try {
DeleteBuilder<T, Integer> builder = getDao(clazz).deleteBuilder();
f.call(builder, new WhereBuilder(builder.where()));
return builder.delete();
} catch (SQLException e) {
throw new UnexpectedException(e);
}
}
@Override
public void close() throws IOException {
connection.close();
}
}