/*
* Copyright (C) 2012 Eyal LEZMY (http://www.eyal.fr)
*
* 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 fr.eyal.lib.data.model;
import java.util.ArrayList;
import java.util.Calendar;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcelable;
import android.os.RemoteException;
import fr.eyal.lib.data.model.provider.BusinessObjectProvider;
import fr.eyal.lib.util.Out;
/**
* This abstract class defines all the DAO functionalities of a {@code BusinessObject}.
* When a class inherits from this class, it is forced to implement the function that allows to store it with the Active Directory pattern.
*
* @author Eyal LEZMY
*/
public abstract class BusinessObjectDAO implements Parcelable {
/**
* Id used when the {@link BusinessObjectDAO} is not filled or saved into the database
*/
public static final int ID_INVALID = -1;
protected ContentResolver mResolver;
/**
* id of the BusinessObject in the database
*/
public long _id;
/**
* updated_at field of the BusinessObject in the database
*/
public Calendar _updatedAt;
/**
* Public constructor to automatically fill the object with the content from the database. By default, this method reach the last modified object if several
* are found
*
* @param id is the "id" primary key of the BusinessObject in the DataLib database
*/
public BusinessObjectDAO(final long id) {
initialize();
fillFromDatabaseById(id);
}
/**
* Public copy constructor
*
* @param obj BusinessObject to copy
*/
public BusinessObjectDAO(final BusinessObjectDAO obj) {
initialize();
}
/**
* Public default constructor
*/
public BusinessObjectDAO() {
initialize();
}
/**
* Initialize the BusinessObject : store the DatabaseAdapter
*/
protected void initialize() {
_id = ID_INVALID;
_updatedAt = Calendar.getInstance();
mResolver = getContentResolver();
}
/********
* DAO
********/
public static final String FIELD_ID = "_id";
public static final String FIELD_UPDATED_AT = "_updated_at";
/**
* This function implements the reaching of the {@link ContentResolver} on the project. Usually this object is accessible by the
* {@link BusinessObjectProvider} class.
*
* @return return the content resolver of the project or <b>null</b> if the {@link ContentProvider} is not declared inside the application
*/
protected abstract ContentResolver getContentResolver();
/**
* This function implements the algorithm to delete all the childs of a business object from the DataLib database.
*
* @return returns the number of row(s) deleted
*/
public abstract ContentValues getContentValues();
/**
* This function implements the saving algorithm to store the whole business object inside the DataLib database.
* It manages the existence of a field with the same id. If the object already exists, it updates the row and all the associated fields (and tables)
*
* @return returns the {@code ContentProviderResult} array produced by the operation
*/
public abstract ContentProviderResult[] save() throws OperationApplicationException, RemoteException;
/**
* This function implements the saving algorithm to store the whole business object inside the DataLib database.
* It manages the existence of a field with the same id. If the object already exists, it updates the row and all the associated fields (and tables)
* @param columns the FIELD_ID to reach before the insertion
* @param where condition to filter the corresponding entry
* @param args arguments to filter the corresponding entry
* @param contentUri Uri of the content
* @param authority Authority of the {@code ContentProvider} to use
*
* @return returns the {@code ContentProviderResult} array produced by the operation
*
* @throws OperationApplicationException
* @throws RemoteException
*/
protected ContentProviderResult[] save(final String[] columns, final String where, final String[] args, final Uri contentUri, final String authority) throws OperationApplicationException, RemoteException {
//we create the DB batch to apply
ArrayList<ContentProviderOperation> batch = new ArrayList<ContentProviderOperation>();
// if the object is supposed to be inside the database
if (_id != ID_INVALID) {
// we check the existence of the entry in the database
final Cursor cursor = mResolver.query(contentUri,
columns, // id
where, // id=?
args, // id
null);
// is the entry exists
if (cursor.getCount() > 0 && cursor.moveToFirst()) {
_id = cursor.getLong(0); // we get the DB id of the object
//we get the update batch
updateIntoTheDatabase(batch);
// else, we add an entry into the database
} else {
//we get the insert batch
addIntoDatabase(batch, contentUri);
}
if (!cursor.isClosed()) {
cursor.close();
}
} else {
addIntoDatabase(batch, contentUri);
}
ContentProviderResult[] result = mResolver.applyBatch(authority, batch);
if(result.length > 0){
long parentIndex = Long.parseLong(result[0].uri.getLastPathSegment());
_id = parentIndex;
//we get the ids list
long[] ids = new long[result.length];
for (int i = 0; i < result.length; i++) {
ids[i] = Integer.parseInt(result[i].uri.getLastPathSegment());
}
updateId(ids, 0);
}
return result;
}
/**
* Update the POJO's {@link BusinessObjectDAO#_id} following a {@link ContentProviderResult} array.
* This function update the current business object and all its children thanks to the ids array.
*
* @param ids the ids list
* @param index index of the id to set into the business object
*
* @return returns the index after increment
*/
public int updateId(long[] ids, int index) {
Out.d("ID", ""+index);
_id = ids[index];
//we update the childs id
updateChildrenId(ids, index+1, index);
return index+1;
}
/**
* Update the {@link BusinessObjectDAO#_id} variable of the business object's children. This function is called
* following a {@link ContentProviderResult} array.<br>
* This function update the children of the current business object and all their children thanks to the ids array.
*
* @param ids the ids list
* @param index index of the id to set into the business object
* @param parentIndex index of the id to set as parentId if it exists. Set it as {@link BusinessObjectDAO#ID_INVALID}
* if there is no parent
*
* @return returns the index after increment
*/
protected abstract int updateChildrenId(long[] ids, int index, int parentIndex);
/**
* Update the POJO's {@link BusinessObjectDAO#_id} following a {@link ContentProviderResult} array.
* This function update the current business object and all its children thanks to the ids array.
*
* @param ids the ids list
* @param index index of the id to set into the business object
*
* @return returns the index after increment
*/
public abstract int updateId(long[] ids, int index, int parentIndex);
/**
* This function implements the deletion algorithm of the business object inside the DataLib database.
*
* @return returns the {@code ContentProviderResult} array produced by the operation
*/
public abstract ContentProviderResult[] delete() throws OperationApplicationException, RemoteException;
/**
* This function implements the deletion algorithm of the business object inside the DataLib database.
*
*
* @param columns the FIELD_ID to reach before the insertion
* @param where condition to filter the corresponding entry
* @param args arguments to filter the corresponding entry
* @param contentUri Uri of the content
* @param authority Authority of the {@code ContentProvider} to use
*
* @return returns the {@code ContentProviderResult} array produced by the operation
*
* @throws OperationApplicationException
* @throws RemoteException
*/
protected ContentProviderResult[] delete(final String[] columns, final String where, final String[] args, final Uri contentUri, final String authority) throws OperationApplicationException, RemoteException {
//we create the DB batch to apply
ArrayList<ContentProviderOperation> batch = new ArrayList<ContentProviderOperation>();
// if the object is supposed to be inside the database
if (_id != ID_INVALID) {
// we check the existence of the entry inside the database
final Cursor cursor = mResolver.query(contentUri, // sensor
columns, // id
where, // _url LIKE ?
args, // url
null);
// is the entry exists
if (cursor.getCount() > 0 && cursor.moveToFirst()) {
//we delete the
deleteChildsFromDatabase(batch);
deleteFromDatabase(batch);
return mResolver.applyBatch(authority, batch);
} else {
return null;
}
} else {
}
return null;
}
/**
* Private function that update the content of the business object into the database
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*/
protected abstract void updateIntoTheDatabase(final ArrayList<ContentProviderOperation> batch);
/**
* Private function that update the content of the business object into the database
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*/
protected void updateIntoTheDatabase(final ArrayList<ContentProviderOperation> batch, final Uri contentUri) {
// we update the update value
_updatedAt = Calendar.getInstance();
// final String where = FIELD_URL + " LIKE ?"; // url LIKE {this.url}
// final String[] args = { _url + "" };
// we get the object's values
final ContentValues values = getContentValues();
// we add the ID inside the Content values
values.put(FIELD_ID, _id);
batch.add(ContentProviderOperation.newUpdate(contentUri)
.withValues(values)
.build());
// mResolver.update(CONTENT_URI, values, where, args);
//TODO improve the childs update with verification or another technique
// we remove all the childs
deleteChildsFromDatabase(batch);
addChildsIntoDatabase(batch);
// // we add the new childs
// for (final Sensor item : sensors) {
// item._parentId = _id;
// item.addIntoDatabase(batch);
// }
}
/**
* This function implements the algorithm to delete the whole business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*
* @return returns if weather the adding instruction have been added
*/
public abstract boolean deleteFromDatabase(final ArrayList<ContentProviderOperation> batch);
/**
* This function implements the algorithm to delete the whole business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*
* @return returns if weather the adding instruction have been added
*/
public boolean deleteFromDatabase(final ArrayList<ContentProviderOperation> batch, final Uri contentUri) {
// if the object is already inside the database
if (!isInvalidID()) {
// we remove the entry inside the database
final String whereClause = FIELD_ID + "=?"; // id=?
final String[] argsClause = { _id + "" };
batch.add(ContentProviderOperation.newDelete(contentUri)
.withSelection(whereClause, argsClause)
.build());
return true;
}
return false;
}
/**
* This function implements the algorithm to delete all the childs of a business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*/
public abstract void deleteChildsFromDatabase(final ArrayList<ContentProviderOperation> batch);
/**
* This function implements the algorithm to store all the childs of a business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*/
public abstract void addChildsIntoDatabase(final ArrayList<ContentProviderOperation> batch);
/**
* This function implements the algorithm to store all the childs of a business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
* @param previousResult the index of the previous operation to refer to in the parent's insertion
*/
public abstract void addChildsIntoDatabase(final ArrayList<ContentProviderOperation> batch, int previousResult);
/**
* This function implements the algorithm to delete all the childs of a business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*/
public void addIntoDatabase(final ArrayList<ContentProviderOperation> batch, final Uri contentUri) {
addIntoDatabase(batch, contentUri, null, -1);
}
/**
* This function implements the algorithm to delete all the childs of a business object from the DataLib database.
* <b>Attention: </b> the query is not performed by this function, you have to use the {@link BusinessObjectProvider#applyBatch(ArrayList)} function to execute it.
*
* @param batch the {@code ContentProviderOperation} container where to add the list of the instructions to perform the action
*/
public void addIntoDatabase(final ArrayList<ContentProviderOperation> batch, final Uri contentUri, final String parentField, final int indexPrevious) {
// on met a jour la date de mise a jour
_updatedAt = Calendar.getInstance();
// on recupere les ContentValues de l'objet
final ContentValues values = getContentValues();
//we get the adding index to set the ValueBackReference for the childrens
int addingIndex = batch.size();
//if we have to add a back reference
if (parentField != null && indexPrevious >= 0) {
ContentProviderOperation op = ContentProviderOperation.newInsert(contentUri)
.withValues(values)
.withValueBackReference(parentField, indexPrevious)
.build();
batch.add(op);
} else {
ContentProviderOperation op = ContentProviderOperation.newInsert(contentUri)
.withValues(values)
.build();
batch.add(op);
}
//then we add its children into the database
addChildsIntoDatabase(batch, addingIndex);
// Out.d(TAG, "Nouvel ID de l'objet : " + _id);
}
/**
* This function implements the algorithm to reach the content form the database and fill the object with the object with the corresponding content
*
* @param id is the id key of the BusinessObject in the database
*/
protected abstract void fillFromDatabaseById(long id);
/**
* This function implements the algorithm to fill an object thanks to a Cursor
*
* @param cursor the cursor containing the data
*/
protected abstract void fillObjectFromCursor(Cursor cursor);
/**
* This function implements the algorithm to fill all the childs of the Business Object thanks to the database
*
* @param cursor the cursor containing the data
*/
protected abstract void fillChildrenFromDatabase();
/*****
* GETTERS & SETTERS
*****/
/**
* Check if the object get a valid ID
*
* @return returns true if the id is not ID_INVALID, otherwise it returns falses
*/
public boolean isInvalidID() {
return _id == ID_INVALID;
}
}