/*
* Funambol is a mobile platform developed by Funambol, Inc.
* Copyright (C) 2011 Funambol, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program 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 General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, see http://www.gnu.org/licenses or write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA.
*
* You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
* 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by Funambol" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by Funambol".
*/
package com.funambol.storage;
import java.io.IOException;
import android.content.Context;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.Cursor;
import com.funambol.util.Log;
public class SQLiteTable extends Table {
private static final String TAG_LOG = "SQLiteTable";
private static final int FUNAMBOL_DATABASE_VERSION = 1;
private static final int KEY_MAX_LENGTH = 128;
private static final int VALUE_MAX_LENGTH = 128;
private Context context;
private String dbName;
private SQLiteDatabase dbStore;
private DatabaseHelper mDatabaseHelper = null;
private final Object lock = new Object();
private int numRefs = 0;
public SQLiteTable(Context context, String dbName, String tableName, int colsType[], int keyIdx) {
this(context, dbName, tableName, colsType, keyIdx, false);
}
public SQLiteTable(Context context, String dbName, String tableName, String colsName[], int colsType[], int keyIdx) {
this(context, dbName, tableName, colsName, colsType, keyIdx, false);
}
public SQLiteTable(Context context, String dbName, String tableName, int colsType[], int keyIdx,
boolean autoincrement)
{
super(tableName, colsType, keyIdx, autoincrement);
this.dbName = dbName;
this.context = context;
}
public SQLiteTable(Context context, String dbName, String tableName, String colsName[], int colsType[], int keyIdx,
boolean autoincrement)
{
super(tableName, colsName, colsType, keyIdx, autoincrement);
this.dbName = dbName;
this.context = context;
}
@Override
public void open() throws IOException {
synchronized(lock) {
numRefs++;
// If the table is already open, we have nothing to do
if (dbStore != null) {
return;
}
mDatabaseHelper = new DatabaseHelper(context, dbName, getName());
// Create the table containing the key value pairs (if it does not exist
// already)
dbStore = mDatabaseHelper.getWritableDatabase();
try {
dbStore.execSQL(getCreateSQLCommand());
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot create table", e);
throw new IOException("Cannot create table " + getName());
}
}
}
@Override
public void close() {
if (dbStore == null) {
return;
}
synchronized(lock) {
numRefs--;
if (numRefs == 0) {
if (dbStore != null) {
dbStore.close();
dbStore = null;
}
}
}
}
@Override
protected void insertTuple(Tuple tuple) throws IOException {
if (dbStore == null) {
throw new IOException("Table must be opened before inserting");
}
ContentValues cv = prepareValue(tuple);
synchronized(lock) {
long rowId = dbStore.insert(getName(), null, cv);
if(rowId != -1) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Insert new record. Key: " + tuple.getKey());
}
// We store in the tuple the assigned key as a return value
if (autoincrement) {
tuple.setField(getKeyIdx(), new Long(rowId));
}
} else {
throw new IOException("Cannot perform insert into table " + getName() + " for key " + tuple.getKey());
}
}
}
@Override
protected void updateTuple(Tuple tuple) throws IOException {
if (dbStore == null) {
throw new IOException("Table must be opened before inserting");
}
Object key = tuple.getKey();
ContentValues cv = prepareValue(tuple);
StringBuffer where = new StringBuffer(getColName(getKeyIdx()));
where.append("=\"").append(key.toString()).append("\"");
synchronized(lock) {
if(dbStore.update(getName(), cv, where.toString(), null) != -1) {
if (Log.isLoggable(Log.TRACE)) {
Log.trace(TAG_LOG, "Update record. Key: " + tuple.getKey());
}
} else {
throw new IOException("Cannot perform update into table " + getName() + " for key " + tuple.getKey());
}
}
}
@Override
protected void deleteTuple(Object key) throws IOException {
if (dbStore == null) {
throw new IOException("Table must be opened before inserting");
}
StringBuffer where = new StringBuffer(getColName(getKeyIdx()));
where.append("=\"").append(key.toString()).append("\"");
synchronized(lock) {
int deleted = dbStore.delete(getName(), where.toString(), null);
if (deleted == 0) {
throw new IOException("Cannot delete row from table " + getName() + " for key " + key);
}
}
}
@Override
public QueryResult query(QueryFilter filter, int orderBy, boolean ascending) throws IOException {
if (dbStore == null) {
throw new IOException("Table must be opened before querying");
}
// In order to be more efficient we translate the QueryFilter into a SQL
// statement
String whereClause = null;
if (filter != null) {
whereClause = filter.getSQLWhereClause(this);
}
String order;
if (orderBy != -1) {
if (ascending) {
order = getColName(orderBy) + " ASC";
} else {
order = getColName(orderBy) + " DESC";
}
} else {
order = null;
}
synchronized(lock) {
Cursor cursor = dbStore.query(getName(),null,whereClause,null,null,null,order);
return new CursorQueryResult(cursor);
}
}
@Override
public void save() throws IOException {
// Nothing to do here
}
@Override
protected void resetTable() throws IOException {
if (dbStore == null) {
throw new IOException("Table must be closed before dropping it");
}
// See SQLiteDatabase documentation to understand the meaning of the "1"
// as where clause
synchronized(lock) {
int deleted = dbStore.delete(getName(), "1", null);
}
}
@Override
protected void dropTable() throws IOException {
if (dbStore != null) {
throw new IOException("Table must be closed before dropping it");
}
synchronized(lock) {
dbStore = mDatabaseHelper.getWritableDatabase();
String sqlDrop = "DROP TABLE " + getName() + ";";
try {
dbStore.execSQL(sqlDrop);
} catch (Exception e) {
Log.error(TAG_LOG, "Cannot drop table", e);
throw new IOException("Cannot drop table " + getName());
} finally {
dbStore.close();
}
}
}
protected String getCreateSQLCommand() {
StringBuffer createStmt = new StringBuffer();
createStmt.append("CREATE TABLE IF NOT EXISTS ").append(getName()).append(" (");
for(int i=0;i<getArity();++i) {
String colName = getColName(i);
if (i>0) {
createStmt.append(",");
}
createStmt.append(colName).append(" ");
if (TYPE_STRING == getColType(i)) {
createStmt.append(" varchar[").append(VALUE_MAX_LENGTH).append("]");
} else if (TYPE_LONG == getColType(i)) {
createStmt.append(" integer");
} else {
throw new IllegalStateException("Invalid table type " + getColType(i));
}
if (i == getKeyIdx()) {
createStmt.append(" PRIMARY KEY");
if (autoincrement) {
if (getColType(getKeyIdx()) != TYPE_LONG) {
throw new IllegalArgumentException("Autoincrement can only be applied to long keys");
}
createStmt.append(" AUTOINCREMENT");
}
}
}
createStmt.append(");");
return createStmt.toString();
}
private ContentValues prepareValue(Tuple tuple) {
ContentValues cv = new ContentValues();
for(int i=0;i<tuple.getArity();++i) {
String v;
boolean skipCol = false;
if (i == getKeyIdx() && autoincrement) {
skipCol = true;
v = null;
} else if (tuple.isUndefined(i)) {
skipCol = true;
v = null;
} else if (tuple.getType(i) == TYPE_STRING) {
v = tuple.getStringField(i);
} else if (tuple.getType(i) == TYPE_LONG) {
v = "" + tuple.getLongField(i);
} else {
throw new IllegalStateException("Unknown field type " + tuple.getType(i));
}
if (!skipCol) {
String colName = getColName(i);
cv.put(colName, v);
}
}
return cv;
}
protected class CursorQueryResult implements QueryResult {
private Cursor cursor;
public CursorQueryResult(Cursor cursor) {
this.cursor = cursor;
}
public boolean hasMoreElements() {
if (cursor.getCount() > 0) {
return cursor.getPosition() < (cursor.getCount() - 1);
} else {
return false;
}
}
public Tuple nextElement() {
boolean ok = cursor.move(1);
if (!ok) {
return null;
}
// Create a Tuple
Tuple res = new Tuple(getColsType(), getKeyIdx(), SQLiteTable.this);
if (cursor.getColumnCount() != getArity()) {
throw new IllegalStateException("Table and cursor number of columns mismatch");
}
for(int i=0;i<getArity();++i) {
int colsType[] = getColsType();
if (colsType[i] == TYPE_STRING) {
res.setField(i, cursor.getString(i));
} else if (colsType[i] == TYPE_LONG) {
res.setField(i, cursor.getLong(i));
} else {
throw new IllegalStateException("Unknown field type " + colsType[i]);
}
}
return res;
}
public void close() {
if (cursor != null) {
cursor.close();
cursor = null;
}
}
public int getCount() {
return cursor.getCount();
}
}
/**
* Helps on creating and upgrading the SQLite db.
*/
private class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String dbName, String tableName) {
super(context, dbName, null, FUNAMBOL_DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}