/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.capedwarf.sqlite; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import org.jboss.capedwarf.common.dto.Identity; import org.jboss.capedwarf.common.sql.SQLObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Abstract SQLite open helper * * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a> */ public abstract class AbstractSQLiteOpenHelper extends SQLiteOpenHelper { private static final String TAG = "AbstractSQLiteOpenHelper"; private static final Map<Class<?>, SQLiteEntityModel> models = new ConcurrentHashMap<Class<?>, SQLiteEntityModel>(); private static ThreadLocal<SQLiteDatabase> dbTL = new ThreadLocal<SQLiteDatabase>(); private volatile boolean initialized; public AbstractSQLiteOpenHelper(Context context, String name, int version) { super(context, name, null, version); } public void onOpen(SQLiteDatabase db) { if (initialized == false && db.isReadOnly() == false) { initialized = true; onCreate(db); } } public void onCreate(SQLiteDatabase db) { for (SQLiteEntityModel em : models.values()) { String table = em.getTable(); if (tableExists(db, em, true)) continue; StringBuilder builder = new StringBuilder("CREATE TABLE "); builder.append(table).append(" ("); String key = em.getKey(); builder.append(key).append(" INTEGER PRIMARY KEY, "); String[] columns = em.getColumnsDescription(); if (columns == null || columns.length == 0) throw new IllegalArgumentException("Illegale columns: " + em); builder.append(columns[0]); for (int i = 1; i < columns.length; i++) { builder.append(", ").append(columns[i]); } builder.append(")"); String sql = builder.toString(); Log.i(TAG, "SQL ==> " + sql); db.execSQL(sql); } } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.i(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); for (SQLiteEntityModel em : models.values()) { String sql = "DROP TABLE IF EXISTS " + em.getTable(); Log.i(TAG, "SQL ==> " + sql); db.execSQL(sql); } onCreate(db); } protected boolean tableExists(SQLiteDatabase db, SQLiteEntityModel em, boolean alter) { String table = em.getTable(); Cursor cursor = db.query("sqlite_master", new String[]{"sql"}, "tbl_name = ?", new String[]{table}, null, null, null); if (cursor == null) return false; try { int count = cursor.getCount(); boolean exists = count > 0; if (exists && alter) { cursor.moveToFirst(); String sql = cursor.getString(0); String[] columns = em.getKeyAndColumns(); String[] desc = em.getColumnsDescription(); for (int i = 0; i < columns.length; i++) { if (sql.contains(columns[i]) == false) { db.execSQL("ALTER TABLE " + table + " ADD COLUMN " + desc[i - 1]); } } } return exists; } finally { cursor.close(); } } @SuppressWarnings({"unchecked"}) public static <T> SQLiteEntityModel<T> getEntityModel(Class<T> entityClass) { SQLiteEntityModel<T> em = models.get(entityClass); if (em == null) { try { if (SQLObject.class.isAssignableFrom(entityClass)) em = new SQLObjectSQLiteEntityModel(entityClass); else em = new ReflectionSQLiteEntityModel<T>(entityClass); models.put(entityClass, em); } catch (Exception e) { throw new RuntimeException(e); } } return em; } @SuppressWarnings({"unchecked"}) public static <T> SQLiteEntityModel<T> putEntityModel(Class<T> clazz, SQLiteEntityModel<T> em) { if (em == null) return models.remove(clazz); else return models.put(clazz, em); } public static <T> T getSingleResult(List<T> results) { return (results == null || results.isEmpty()) ? null : results.get(0); } public static SQLiteDatabase getDB() { SQLiteDatabase db = dbTL.get(); if (db == null) throw new IllegalArgumentException("Missing DB, forgot begin()?"); return db; } public void begin() { SQLiteDatabase db = getWritableDatabase(); dbTL.set(db); db.beginTransaction(); } public void commit() { SQLiteDatabase db = getDB(); db.setTransactionSuccessful(); } public void rollback() { } public void end() { SQLiteDatabase db = getDB(); dbTL.remove(); db.endTransaction(); } public boolean initialize(int currentVersion) { SQLiteDatabase db = getWritableDatabase(); int version = db.getVersion(); boolean isNew = currentVersion > version; if (isNew) onUpgrade(db, version, currentVersion); return isNew; } public <T> T load(Class<T> entityClass, long pk) { return load(getReadableDatabase(), entityClass, pk); } public static long persist(SQLObject entity) { if (entity == null) return -1; SQLiteDatabase db = getDB(); return insertOrUpdate(db, entity); } public static int update(SQLObject entity) { if (entity == null) return 0; SQLiteDatabase db = getDB(); return update(db, getEntityModel(entity.getClass()), entity, entity.getPk(), "pk"); } public static int delete(SQLObject entity) { SQLiteDatabase db = getDB(); return delete(db, entity, "pk = ?", entity.getPk()); } public <T> List<T> select(Class<T> entityClass, String selection, String[] selectionArgs, String orderBy, String limit) { SQLiteDatabase db = getReadableDatabase(); return select(db, entityClass, selection, selectionArgs, orderBy, limit); } public <T> List select(Class<T> entityClass, List<ColumnMapper> mappers, String selection, String[] selectionArgs, String orderBy, String limit) { SQLiteDatabase db = getReadableDatabase(); return select(db, entityClass, mappers, selection, selectionArgs, orderBy, limit); } public <T> List<Long> pks(Class<T> entityClass, String selection, String[] selectionArgs, String orderBy, String limit) { SQLiteDatabase db = getReadableDatabase(); return pks(db, entityClass, selection, selectionArgs, orderBy, limit); } public int count(Class<?> entityClass, String selection, String[] selectionArgs) { SQLiteDatabase db = getReadableDatabase(); return count(db, entityClass, selection, selectionArgs); } // --- helper methods public static <T> T load(SQLiteDatabase db, Class<T> entityClass, Long pk) { SQLiteEntityModel<T> em = getEntityModel(entityClass); return getSingleResult(select(db, em, em.getKey() + " = ?", toSelectionArgs(pk), null, "1")); } public static <T extends Identity> T loadIdentity(SQLiteDatabase db, Class<T> entityClass, Long id) { SQLiteEntityModel<T> em = getEntityModel(entityClass); return getSingleResult(select(db, em, "id = ?", toSelectionArgs(id), null, "1")); } public static <T> List<T> select(SQLiteDatabase db, Class<T> entityClass, String selection, Object selectionArg) { return select(db, entityClass, selection, toSelectionArgs(selectionArg), null, null); } public static <T> List<T> select(SQLiteDatabase db, Class<T> entityClass, String selection, String[] selectionArgs, String orderBy, String limit) { SQLiteEntityModel<T> em = getEntityModel(entityClass); return select(db, em, selection, selectionArgs, orderBy, limit); } public static <T> List select(SQLiteDatabase db, Class<T> entityClass, List<ColumnMapper> mappers, String selection, String[] selectionArgs, String orderBy, String limit) { SQLiteEntityModel<T> em = getEntityModel(entityClass); return select(db, em.getTable(), mappers, selection, selectionArgs, orderBy, limit); } @SuppressWarnings({"unchecked"}) public static <T> List<Long> pks(SQLiteDatabase db, Class<T> entityClass, String selection, String[] selectionArgs, String orderBy, String limit) { final SQLiteEntityModel<T> em = getEntityModel(entityClass); ColumnMapper idMapper = new ColumnMapper<Long>() { public String column() { return em.getKey(); } public Long value(Cursor cursor, int i) { return cursor.getLong(i); } }; return (List<Long>) select(db, em.getTable(), Collections.singletonList(idMapper), selection, selectionArgs, orderBy, limit); } @SuppressWarnings({"unchecked"}) private static List select(SQLiteDatabase db, String table, List<ColumnMapper> mappers, String selection, String[] selectionArgs, String orderBy, String limit) { int n = mappers.size(); if (n == 0) throw new IllegalArgumentException("Empty mappers!"); String[] columns = new String[n]; for (int i = 0; i < n; i++) columns[i] = mappers.get(i).column(); Cursor cursor = db.query(table, columns, selection, selectionArgs, null, null, orderBy, limit); cursor = AbstractSQLiteEntityModel.toFirst(cursor); if (cursor == null) return Collections.emptyList(); List result = new ArrayList(); try { do { if (n > 1) { Object[] array = new Object[n]; for (int index = 0; index < n; index++) { array[index] = mappers.get(index).value(cursor, index); } result.add(array); } else { result.add(mappers.get(0).value(cursor, 0)); } } while (cursor.moveToNext()); return result; } finally { cursor.close(); } } @SuppressWarnings({"unchecked"}) public static <T> List<T> select(SQLiteDatabase db, SQLiteEntityModel<T> em, String selection, String[] selectionArgs, String orderBy, String limit) { EntityListener listener = em.onLoad(); if (listener != null) listener.action(db, null, Phase.BEFORE); Cursor cursor = db.query(em.getTable(), em.getKeyAndColumns(), selection, selectionArgs, null, null, orderBy, limit); List<T> result = em.readCursor(cursor); if (listener != null && result.isEmpty() == false) { for (T entity : result) listener.action(db, entity, Phase.AFTER); } return result; } public static long insertOrUpdate(SQLiteDatabase db, SQLObject entity) { return insertOrUpdate(db, entity, entity.getPk(), "pk"); } public static long insertOrUpdate(SQLiteDatabase db, Object entity, Long pk, String key) { SQLiteEntityModel em = getEntityModel(entity.getClass()); if (pk == null) { pk = insert(db, em, entity); } else { update(db, em, entity, pk, key); } return pk != null ? pk : -1; } @SuppressWarnings({"unchecked"}) public static Long insert(SQLiteDatabase db, SQLiteEntityModel em, Object entity) { EntityListener listener = em.onInsert(); if (listener != null) listener.action(db, entity, Phase.BEFORE); Long pk = db.insert(em.getTable(), null, em.getContentValues(entity)); em.setKeyValue(entity, pk); if (pk < 0) Log.v(TAG, "Error inserting new entity: " + entity); if (listener != null && pk != -1) listener.action(db, entity, Phase.AFTER); return pk; } @SuppressWarnings({"unchecked"}) public static int update(SQLiteDatabase db, SQLiteEntityModel em, Object entity, Long pk, String key) { EntityListener listener = em.onUpdate(); if (listener != null) listener.action(db, entity, Phase.BEFORE); int result = db.update(em.getTable(), em.getContentValues(entity), key + " = ?", toSelectionArgs(pk)); if (result <= 0) Log.v(TAG, "No matching entity to update: " + em + ", pk=" + pk); if (listener != null && result > 0) listener.action(db, entity, Phase.AFTER); return result; } @SuppressWarnings({"unchecked"}) public static int delete(SQLiteDatabase db, Object entity, String selection, Object selectionArg) { SQLiteEntityModel em = getEntityModel(entity.getClass()); EntityListener listener = em.onDelete(); if (listener != null) listener.action(db, entity, Phase.BEFORE); int result = db.delete(em.getTable(), selection, toSelectionArgs(selectionArg)); if (listener != null && result > 0) listener.action(db, entity, Phase.AFTER); return result; } @SuppressWarnings({"unchecked"}) public static int count(SQLiteDatabase db, Class<?> entityClass, String selection, String[] selectionArgs) { SQLiteEntityModel em = getEntityModel(entityClass); Cursor cursor = db.query(em.getTable(), new String[]{em.getKey()}, selection, selectionArgs, null, null, null); try { return (cursor != null) ? cursor.getCount() : 0; } finally { if (cursor != null) cursor.close(); } } public static String toQuery(Iterable arg) { StringBuilder builder = new StringBuilder(); //noinspection UnusedDeclaration for (Object i : arg) { if (builder.length() > 0) builder.append(","); builder.append("?"); } return builder.toString(); } public static String[] toSelectionArgs(Object... args) { if (args == null) return null; List<String> results = new ArrayList<String>(); for (Object arg : args) { if (arg instanceof Iterable) { for (Object it : (Iterable) arg) results.add(toSelectionArg(it)); } else { results.add(toSelectionArg(arg)); } } return results.toArray(new String[results.size()]); } private static String toSelectionArg(Object arg) { if (Boolean.class.isInstance(arg)) { arg = (Boolean.TRUE.equals(arg)) ? 1 : 0; } else if (Enum.class.isInstance(arg)) { arg = ((Enum) arg).ordinal(); } return (arg == null) ? null : String.valueOf(arg); } }