package by.istin.android.xcore.provider;
import android.content.*;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import by.istin.android.xcore.ContextHolder;
import by.istin.android.xcore.db.DBHelper;
import by.istin.android.xcore.preference.PreferenceHelper;
import by.istin.android.xcore.provider.ModelContract.ModelColumns;
import by.istin.android.xcore.source.DataSourceRequest;
import by.istin.android.xcore.source.DataSourceRequestEntity;
import by.istin.android.xcore.source.SyncDataSourceRequestEntity;
import by.istin.android.xcore.utils.Log;
import by.istin.android.xcore.utils.StringUtil;
public abstract class ModelContentProvider extends ContentProvider {
public static final String OLD_APP_VERSION = "oldAppVersion";
private static UriMatcher sUriMatcher;
private static final int MODELS = 1;
private static final int MODELS_ID = 2;
private static final int MODELS_ID_NEGOTIVE = 3;
private static DBHelper sDbHelper;
private volatile Boolean isLock = false;
private static volatile Object mLock = new Object();
@Override
public boolean onCreate() {
initUriMatcher();
Log.init(getContext());
Context context = ContextHolder.getInstance().getContext();
if (context == null) {
ContextHolder.getInstance().setContext(getContext());
}
synchronized (mLock) {
if (sDbHelper != null) {
//check for only one instance of helper
//2.3 android issue, we can have 2 calls of this method
return true;
}
sDbHelper = new DBHelper(getContext());
if (Log.isOff) {
int currentAppVersion = getAppVersion();
int oldAppVersion = PreferenceHelper.getInt(OLD_APP_VERSION, 0);
if (currentAppVersion == oldAppVersion) {
//return true;
} else {
PreferenceHelper.set(OLD_APP_VERSION, currentAppVersion);
onUpgrade(oldAppVersion, currentAppVersion);
}
}
Class<?>[] dbEntities = getDbEntities();
sDbHelper.createTablesForModels(DataSourceRequestEntity.class);
sDbHelper.createTablesForModels(SyncDataSourceRequestEntity.class);
sDbHelper.createTablesForModels(dbEntities);
return true;
}
}
protected void onUpgrade(int oldAppVersion, int currentAppVersion) {
Log.xd(this, "onUpgrade: from "+oldAppVersion + " to " + currentAppVersion);
}
public int getAppVersion() {
try {
PackageInfo pInfo = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0);
return pInfo.versionCode;
} catch (PackageManager.NameNotFoundException e) {
//can be ignored
}
return 0;
}
private void initUriMatcher() {
if (sUriMatcher == null) {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
String authority = ModelContract.getAuthority(getContext());
sUriMatcher.addURI(authority, "*", MODELS);
sUriMatcher.addURI(authority, "*/#", MODELS_ID);
//for negotive number
sUriMatcher.addURI(authority, "*/*", MODELS_ID_NEGOTIVE);
}
}
public abstract Class<?>[] getDbEntities();
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case MODELS:
return ModelContract.getContentType(uri.getLastPathSegment());
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
synchronized (mLock) {
Log.xd(this, " contentprovider " + this);
return deleteWithoutLockCheck(sDbHelper, uri, where, whereArgs, true);
}
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
synchronized (mLock) {
return insertWithoutLockCheck(sDbHelper, uri, initialValues, true);
}
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
String className = uri.getLastPathSegment();
try {
synchronized (mLock) {
return bulkInsert(uri, values, className, sDbHelper);
}
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return queryWithoutLock(uri, projection, selection, selectionArgs, sortOrder);
}
//TODO refactoring query parameters to path segments
private Cursor queryWithoutLock(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
String className = null;
List<String> pathSegments = null;
switch (sUriMatcher.match(uri)) {
case MODELS:
className = uri.getLastPathSegment();
break;
case MODELS_ID:
pathSegments = uri.getPathSegments();
className = pathSegments.get(pathSegments.size()-2);
if (StringUtil.isEmpty(selection)) {
selection = ModelColumns._ID + " = " + uri.getLastPathSegment();
} else {
selection = selection + ModelColumns._ID + " = " + uri.getLastPathSegment();
}
break;
case MODELS_ID_NEGOTIVE:
pathSegments = uri.getPathSegments();
className = pathSegments.get(pathSegments.size()-2);
if (StringUtil.isEmpty(selection)) {
selection = ModelColumns._ID + " = " + uri.getLastPathSegment();
} else {
selection = selection + ModelColumns._ID + " = " + uri.getLastPathSegment();
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (ModelContract.isSqlUri(className)) {
Cursor c = sDbHelper.rawQuery(ModelContract.getSqlParam(uri), selectionArgs);
if (c != null) {
c.getCount();
c.moveToFirst();
}
Uri observerUri = ModelContract.getObserverUri(uri);
if (observerUri != null) {
c.setNotificationUri(getContext().getContentResolver(), observerUri);
}
return c;
} else {
try {
String offsetParameter = uri.getQueryParameter("offset");
String sizeParameter = uri.getQueryParameter("size");
String limitParam = null;
if (!StringUtil.isEmpty(offsetParameter) && !StringUtil.isEmpty(sizeParameter)) {
limitParam = StringUtil.format("%s,%s",offsetParameter, sizeParameter);
}
Cursor c = sDbHelper.query(Class.forName(className), projection, selection, selectionArgs, null, null, sortOrder, limitParam);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
c.getCount();
c.moveToFirst();
}
return c;
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
}
private int bulkInsert(Uri uri, ContentValues[] values, String className, DBHelper helper) throws ClassNotFoundException {
int count = helper.updateOrInsert(getDataSourceRequestFromUri(uri), Class.forName(className), values);
if (count > 0) {
if (ModelContract.isNotify(uri)) {
getContext().getContentResolver().notifyChange(uri, null);
}
}
return count;
}
public static DataSourceRequest getDataSourceRequestFromUri(Uri uri) {
String dataSourceRequest = ModelContract.getDataSourceRequest(uri);;
if (!StringUtil.isEmpty(dataSourceRequest)) {
return DataSourceRequest.fromUri(Uri.parse("content://temp?"+StringUtil.decode(dataSourceRequest)));
}
return null;
}
private int deleteWithoutLockCheck(DBHelper helper, Uri uri, String where, String[] whereArgs, boolean isNotify) {
List<String> pathSegments = uri.getPathSegments();
String className = StringUtil.EMPTY;
switch (sUriMatcher.match(uri)) {
case MODELS:
className = pathSegments.get(pathSegments.size()-1);
break;
case MODELS_ID:
className = pathSegments.get(pathSegments.size()-2);
if (where == null) {
where = StringUtil.EMPTY;
}
where = where + ModelColumns._ID + " = " + uri.getLastPathSegment();
break;
case MODELS_ID_NEGOTIVE:
className = pathSegments.get(pathSegments.size()-2);
if (where == null) {
where = StringUtil.EMPTY;
}
where = where + ModelColumns._ID + " = " + uri.getLastPathSegment();
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
int count = helper.delete(Class.forName(className), where, whereArgs);
if (ModelContract.isNotify(uri) && isNotify) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
private Uri insertWithoutLockCheck(DBHelper helper, Uri uri, ContentValues initialValues, boolean isNotify) {
if (sUriMatcher.match(uri) != MODELS) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
String className = uri.getLastPathSegment();
try {
DataSourceRequest dataSourceRequestFromUri = getDataSourceRequestFromUri(uri);
Class<?> classOfModel = Class.forName(className);
long rowId = helper.updateOrInsert(dataSourceRequestFromUri, classOfModel, initialValues);
if (rowId != -1l) {
Uri serializableModelUri = ContentUris.withAppendedId(uri, rowId);
if (ModelContract.isNotify(uri) && isNotify) {
getContext().getContentResolver().notifyChange(
serializableModelUri, null);
}
return serializableModelUri;
} else {
throw new IllegalArgumentException(uri + ": " + initialValues.toString());
}
//TODO need check throw new SQLException("Failed to insert row into " + uri);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public int update(Uri uri, ContentValues initialValues, String where,
String[] whereArgs) {
throw new UnsupportedOperationException("unsupported operation, please use insert method");
}
@Override
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
synchronized (isLock) {
isLock = true;
}
try {
synchronized (mLock) {
isLock = true;
sDbHelper.lockTransaction();
ContentProviderResult[] result = new ContentProviderResult[operations.size()];
try {
Set<Uri> set = new HashSet<Uri>();
for(int i = 0; i < operations.size(); i++) {
ContentProviderOperation contentProviderOperation = operations.get(i);
Uri uri = contentProviderOperation.getUri();
result[i] = apply(contentProviderOperation, result, i);
set.add(uri);
}
sDbHelper.unlockTransaction();
for (Iterator<Uri> iterator = set.iterator(); iterator.hasNext(); ) {
Uri uri = iterator.next();
getContext().getContentResolver().notifyChange(Uri.parse(uri.toString().split("\\?")[0]), null);
}
} catch (OperationApplicationException e1) {
sDbHelper.errorUnlockTransaction();
throw e1;
} catch (Exception e) {
sDbHelper.errorUnlockTransaction();
throw new IllegalArgumentException(e);
}
return result;
}
} finally {
synchronized (isLock) {
isLock = false;
}
}
}
public ContentProviderResult apply(ContentProviderOperation contentProviderOperation, ContentProviderResult[] backRefs,
int numBackRefs) throws OperationApplicationException {
ContentValues values = contentProviderOperation.resolveValueBackReferences(backRefs, numBackRefs);
String[] selectionArgs = contentProviderOperation.resolveSelectionArgsBackReferences(backRefs, numBackRefs);
Uri mUri = contentProviderOperation.getUri();
if (values != null) {
Uri newUri = insertWithoutLockCheck(sDbHelper, mUri, values, false);
if (newUri == null) {
throw new OperationApplicationException("insert failed");
}
return new ContentProviderResult(newUri);
}
int numRows = deleteWithoutLockCheck(sDbHelper, mUri, "?", selectionArgs, false);
return new ContentProviderResult(numRows);
}
}