/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.justeat.mickeydb;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import com.justeat.mickeydb.util.Closeables;
/**
* <p>Provides default implementation for CRUD content provider actions</p>
*
* <p>Has two operating modes, one with URI's containing ID's, enabled by
* setting forUrisWithId to true during construction, or URI's without ID's,
* by setting forUrisWithId to false.
* </p>
*
* <p>Since content providers delegate all queries to actions, including active record queries,
* this class requires an ActiveRecordFactory during construction so it knows how
* to create ActiveRecord instances.</p>
*
* @see ContentProviderActions
*/
public class DefaultContentProviderActions extends ContentProviderActions {
private String mSource;
private ActiveRecordFactory<?> mRecordFactory;
private boolean mForUrisWithId;
/**
* <p>Create new default actions</p>
* @param source The source table or view name
* @param forUrisWithId Wether expected URI's have appended id's, if so
* then id's will be parsed and used in all CRUD actions
*/
public DefaultContentProviderActions(String source, boolean forUrisWithId) {
this(source, forUrisWithId, null);
}
/**
* <p>Create new default actions</p>
* @param source The source table or view name
* @param forUrisWithId Whether expected URI's have appended id's, if so
* then id's will be parsed and used in all CRUD actions
* @param recordFactory A factory for creating ActiveRecord's, factories will be generated for
* each generated ActiveRecord
*/
public <T extends ActiveRecord> DefaultContentProviderActions(String source, boolean forUrisWithId, ActiveRecordFactory<T> recordFactory) {
mSource = source;
mForUrisWithId = forUrisWithId;
mRecordFactory = recordFactory;
}
@Override
public int delete(MickeyContentProvider provider, Uri uri, String selection, String[] selectionArgs){
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
if(mForUrisWithId) {
long id = ContentUris.parseId(uri);
int affected = new Query(provider.getContext(), null)
.expr(BaseColumns._ID, Query.Op.EQ, id)
.append(selection, selectionArgs)
.delete(db, mSource);
return affected;
} else {
return db.delete(mSource, selection, selectionArgs);
}
}
@Override
public Uri insert(MickeyContentProvider provider, Uri uri, ContentValues values){
if(mForUrisWithId) {
return null; // Not applicable for uris with id
}
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
long id = db.insertOrThrow(mSource, null, values);
if(id > -1) {
return ContentUris.withAppendedId(uri, id);
}
return null;
}
@Override
public int update(MickeyContentProvider provider, Uri uri, ContentValues values, String selection, String[] selectionArgs){
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
if(mForUrisWithId) {
long id = ContentUris.parseId(uri);
int affected = new Query(provider.getContext(), null)
.expr(BaseColumns._ID, Query.Op.EQ, id)
.append(selection, selectionArgs)
.update(db, mSource, values);
return affected;
}
else {
return db.update(mSource, values, selection, selectionArgs);
}
}
@Override
public Cursor query(MickeyContentProvider provider, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
String groupBy = uri.getQueryParameter(MickeyContentProvider.PARAM_GROUP_BY);
String limit = uri.getQueryParameter(MickeyContentProvider.PARAM_LIMIT);
String offset = uri.getQueryParameter(MickeyContentProvider.PARAM_OFFSET);
String sortAndLimitClause = buildSortAndClause(sortOrder, limit, offset);
if(mForUrisWithId) {
long id = ContentUris.parseId(uri);
return new Query(provider.getContext(), null)
.expr(BaseColumns._ID, Query.Op.EQ, id)
.append(selection, selectionArgs)
.query(db, mSource, projection, sortOrder, TextUtils.isEmpty(groupBy) ? null : groupBy);
} else {
return db.query(mSource, projection, selection, selectionArgs, TextUtils.isEmpty(groupBy) ? null : groupBy, null, sortAndLimitClause);
}
}
public static String buildSortAndClause(String sortOrder, String limit, String offset) {
String limitClause = null;
if(!TextUtils.isEmpty(limit)) {
limitClause = " LIMIT " + limit;
}
if(!TextUtils.isEmpty(offset)) {
limitClause = limitClause + " OFFSET " + offset;
}
if(limitClause != null) {
return (sortOrder == null ? " 1" + limitClause : sortOrder + limitClause);
} else {
return sortOrder;
}
}
@Override
public int bulkInsert(MickeyContentProvider provider, Uri uri, ContentValues[] values) {
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
int numValues = values.length;
try {
db.beginTransaction();
for (int i = 0; i < numValues; i++) {
db.insertOrThrow(mSource, null, values[i]);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return numValues;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ActiveRecord> List<T> selectRecords(MickeyContentProvider provider, Uri uri, Query sQuery, String sortOrder) {
if(mRecordFactory == null) {
return null;
}
String groupBy = uri.getQueryParameter(MickeyContentProvider.PARAM_GROUP_BY);
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
Cursor c = null;
ArrayList<T> items = new ArrayList<T>();
try {
c = db.query(mSource, mRecordFactory.getProjection(), sQuery.toString(), sQuery.getArgsArray(), TextUtils.isEmpty(groupBy) ? null : groupBy, null, sortOrder);
if(c.getCount() > 0) {
while(c.moveToNext()) {
items.add((T)mRecordFactory.create(c));
}
}
} finally {
Closeables.closeSilently(c);
}
return items;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ActiveRecord> Map<String, T> selectRecordMap(
MickeyContentProvider provider, Uri uri, Query sQuery,
String keyColumnName) {
if(mRecordFactory == null) {
return null;
}
String groupBy = uri.getQueryParameter(MickeyContentProvider.PARAM_GROUP_BY);
final SQLiteDatabase db = provider.getOpenHelper().getWritableDatabase();
Cursor c = null;
HashMap<String, T> items = new HashMap<String, T>();
try {
c = db.query(mSource, mRecordFactory.getProjection(), sQuery.toString(), sQuery.getArgsArray(), TextUtils.isEmpty(groupBy) ? null : groupBy, null, null);
int keyColumnIndex = c.getColumnIndexOrThrow(keyColumnName);
if(c.getCount() > 0) {
while(c.moveToNext()) {
items.put(c.getString(keyColumnIndex), (T)mRecordFactory.create(c));
}
}
} finally {
Closeables.closeSilently(c);
}
return items;
}
}