package org.ovirt.mobile.movirt.provider; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.Context; import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import org.androidannotations.annotations.AfterInject; import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.RootContext; import org.ovirt.mobile.movirt.model.base.BaseEntity; import org.ovirt.mobile.movirt.model.mapping.EntityMapper; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @EBean public class ProviderFacade { public static final String TAG = ProviderFacade.class.getSimpleName(); @RootContext Context context; private ContentProviderClient contentClient; @AfterInject void initContentProviderClient() { contentClient = context.getContentResolver().acquireContentProviderClient(OVirtContract.BASE_CONTENT_URI); } public class QueryBuilder<E extends BaseEntity<?>> { private static final String URI_FIELD_NAME = "CONTENT_URI"; private final Class<E> clazz; private final Uri baseUri; StringBuilder selection = new StringBuilder(); List<String> selectionArgs = new ArrayList<>(); StringBuilder sortOrder = new StringBuilder(); String limitClause = ""; String[] projection; public QueryBuilder(Class<E> clazz) { this.clazz = clazz; try { this.baseUri = (Uri) clazz.getField(URI_FIELD_NAME).get(null); } catch (Exception e) { // NoSuchFieldException | IllegalAccessException - since SDK version 19 throw new RuntimeException("Assertion error: Class: " + clazz + " does not define static field " + URI_FIELD_NAME, e); } } public QueryBuilder<E> projection(String[] projection) { this.projection = projection; return this; } public QueryBuilder<E> id(String value) { return where(OVirtContract.BaseEntity.ID, value); } public QueryBuilder<E> whereLike(String columnName, String value) { return where(columnName, value, Relation.IS_LIKE); } public QueryBuilder<E> where(String columnName, String value) { return where(columnName, value, Relation.IS_EQUAL); } public QueryBuilder<E> whereIn(String columnName, String[] values) { if (selection.length() > 0) { selection.append(" AND "); } selection.append(columnName); if (values == null || values.length == 0) { selection.append(" IS NULL "); } else { selection.append(Relation.IN.getVal()).append(" ("); for (int i = 0; i < values.length; i++) { selection.append("? "); if (i == values.length - 1) { selection.append(") "); } else { selection.append(", "); } } selectionArgs.addAll(Arrays.asList(values)); } return this; } public QueryBuilder<E> empty(String columnName) { if (selection.length() > 0) { selection.append(" AND "); } selection.append('(').append(columnName).append(" IS NULL OR ") .append(columnName).append(Relation.IS_EQUAL.getVal()).append("'') "); return this; } public QueryBuilder<E> whereNotEqual(String columnName, String value) { return where(columnName, value, Relation.NOT_EQUAL); } public QueryBuilder<E> where(String columnName, String value, Relation relation) { assert !columnName.equals("") : "columnName cannot be empty or null"; if (selection.length() > 0) { selection.append(" AND "); } selection.append(columnName); if (value == null) { selection.append(" IS NULL "); } else { selection.append(relation.getVal()).append("? "); selectionArgs.add(value); } return this; } public QueryBuilder<E> orderBy(String columnName) { return orderBy(columnName, SortOrder.ASCENDING); } public QueryBuilder<E> orderByAscending(String columnName) { return orderBy(columnName); } public QueryBuilder<E> orderByDescending(String columnName) { return orderBy(columnName, SortOrder.DESCENDING); } public QueryBuilder<E> limit(int limit) { limitClause = " LIMIT " + Integer.toString(limit); return this; } private String sortOrderWithLimit() { StringBuilder res = new StringBuilder(); String sortOrderString = sortOrder.toString(); res.append(!"".equals(sortOrderString) ? sortOrderString : OVirtContract.ROW_ID); res.append(limitClause); return res.toString(); } public QueryBuilder<E> orderBy(String columnName, SortOrder order) { assert !columnName.equals("") : "columnName cannot be empty or null"; if (sortOrder.length() > 0) { sortOrder.append(", "); } sortOrder.append(columnName).append(" "); sortOrder.append(order).append(" "); return this; } public Cursor asCursor() { try { return contentClient.query(baseUri, projection, selection.toString(), selectionArgs.toArray(new String[selectionArgs.size()]), sortOrderWithLimit()); } catch (RemoteException e) { Log.e(TAG, "Error querying " + baseUri, e); throw new RuntimeException(e); } } public Loader<Cursor> asLoader() { return new CursorLoader(context, baseUri, projection, selection.toString(), selectionArgs.toArray(new String[selectionArgs.size()]), sortOrderWithLimit()); } public Collection<E> all() { Cursor cursor = asCursor(); if (cursor == null) { return Collections.emptyList(); } List<E> result = new ArrayList<>(); while (cursor.moveToNext()) { result.add(EntityMapper.forEntity(clazz).fromCursor(cursor)); } cursor.close(); return result; } public E first() { Cursor cursor = asCursor(); if (cursor == null) { return null; } E entity = cursor.moveToNext() ? EntityMapper.forEntity(clazz).fromCursor(cursor) : null; cursor.close(); return entity; } } public class BatchBuilder { private ArrayList<ContentProviderOperation> batch = new ArrayList<>(); public <E extends BaseEntity<?>> BatchBuilder insert(E entity) { batch.add(ContentProviderOperation.newInsert(entity.getBaseUri()).withValues(entity.toValues()).build()); return this; } public <E extends BaseEntity<?>> BatchBuilder update(E entity) { batch.add(ContentProviderOperation.newUpdate(entity.getUri()).withValues(entity.toValues()).build()); return this; } public <E extends BaseEntity<?>> BatchBuilder delete(E entity) { batch.add(ContentProviderOperation.newDelete(entity.getUri()).build()); return this; } public void apply() { try { contentClient.applyBatch(batch); } catch (RemoteException | OperationApplicationException e) { throw new RuntimeException("Batch apply failed", e); } } public boolean isEmpty() { return batch.isEmpty(); } } public <E extends BaseEntity<?>> QueryBuilder<E> query(Class<E> clazz) { return new QueryBuilder<>(clazz); } public <E extends BaseEntity<?>> void insert(E entity) { try { contentClient.insert(entity.getBaseUri(), entity.toValues()); } catch (RemoteException e) { Log.e(TAG, "Error inserting entity: " + entity, e); } } public <E extends BaseEntity<?>> void update(E entity) { try { contentClient.update(entity.getUri(), entity.toValues(), null, null); } catch (RemoteException e) { Log.e(TAG, "Error updating entity: " + entity, e); } } public <E extends BaseEntity<?>> void delete(E entity) { deleteAll(entity.getUri()); } public int deleteAll(Uri uri) { try { return contentClient.delete(uri, null, null); } catch (RemoteException e) { Log.e(TAG, "Error deleting entities with uri: " + uri, e); } return -1; } public void deleteEventsAndLetOnly(int leave) { int id = getSmallestFrom(leave); if (id != 0) { try { contentClient.delete(OVirtContract.Event.CONTENT_URI, OVirtContract.Event.ID + " < ?", new String[]{Integer.toString(id)} ); } catch (RemoteException e) { Log.e(TAG, "Error deleting events", e); throw new RuntimeException(e); } } } public BatchBuilder batch() { return new BatchBuilder(); } public int deleteEvents() { return deleteAll(OVirtContract.Event.CONTENT_URI); } private int getSmallestFrom(int from) { try { Cursor cursor = contentClient.query(OVirtContract.Event.CONTENT_URI, new String[]{OVirtContract.Event.ID}, null, null, OVirtContract.Event.ID + " DESC LIMIT " + from); if (cursor.moveToLast()) { return cursor.getInt(0); } } catch (RemoteException e) { Log.e(TAG, "Error determining last event id", e); throw new RuntimeException(e); } return 0; } public int getLastEventId() { try { Cursor cursor = contentClient.query(OVirtContract.Event.CONTENT_URI, new String[]{"MAX(" + OVirtContract.Event.ID + ")"}, null, null, null); if (cursor.moveToNext()) { return cursor.getInt(0); } } catch (RemoteException e) { Log.e(TAG, "Error determining last event id", e); throw new RuntimeException(e); } return 0; } }