
com.path.android.jobqueue.persistentQueue.sqlite.SqliteJobQueue Maven / Gradle / Ivy
package com.path.android.jobqueue.persistentQueue.sqlite;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteStatement;
import com.path.android.jobqueue.BaseJob;
import com.path.android.jobqueue.JobHolder;
import com.path.android.jobqueue.JobManager;
import com.path.android.jobqueue.JobQueue;
import com.path.android.jobqueue.log.JqLog;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Collection;
/**
* Persistent Job Queue that keeps its data in an sqlite database.
*/
public class SqliteJobQueue implements JobQueue {
DbOpenHelper dbOpenHelper;
private final long sessionId;
SQLiteDatabase db;
SqlHelper sqlHelper;
JobSerializer jobSerializer;
QueryCache readyJobsQueryCache;
QueryCache nextJobsQueryCache;
/**
* @param context application context
* @param sessionId session id should match {@link JobManager}
* @param id uses this value to construct database name {@code "db_" + id}
*/
public SqliteJobQueue(Context context, long sessionId, String id, JobSerializer jobSerializer) {
this.sessionId = sessionId;
dbOpenHelper = new DbOpenHelper(context, "db_" + id);
db = dbOpenHelper.getWritableDatabase();
sqlHelper = new SqlHelper(db, DbOpenHelper.JOB_HOLDER_TABLE_NAME, DbOpenHelper.ID_COLUMN.columnName, DbOpenHelper.COLUMN_COUNT, sessionId);
this.jobSerializer = jobSerializer;
readyJobsQueryCache = new QueryCache();
nextJobsQueryCache = new QueryCache();
sqlHelper.resetDelayTimesTo(JobManager.NOT_DELAYED_JOB_DELAY);
}
/**
* {@inheritDoc}
*/
@Override
public long insert(JobHolder jobHolder) {
SQLiteStatement stmt = sqlHelper.getInsertStatement();
long id;
synchronized (stmt) {
stmt.clearBindings();
bindValues(stmt, jobHolder);
id = stmt.executeInsert();
}
jobHolder.setId(id);
return id;
}
private void bindValues(SQLiteStatement stmt, JobHolder jobHolder) {
if (jobHolder.getId() != null) {
stmt.bindLong(DbOpenHelper.ID_COLUMN.columnIndex + 1, jobHolder.getId());
}
stmt.bindLong(DbOpenHelper.PRIORITY_COLUMN.columnIndex + 1, jobHolder.getPriority());
if(jobHolder.getGroupId() != null) {
stmt.bindString(DbOpenHelper.GROUP_ID_COLUMN.columnIndex + 1, jobHolder.getGroupId());
}
stmt.bindLong(DbOpenHelper.RUN_COUNT_COLUMN.columnIndex + 1, jobHolder.getRunCount());
byte[] baseJob = getSerializeBaseJob(jobHolder);
if (baseJob != null) {
stmt.bindBlob(DbOpenHelper.BASE_JOB_COLUMN.columnIndex + 1, baseJob);
}
stmt.bindLong(DbOpenHelper.CREATED_NS_COLUMN.columnIndex + 1, jobHolder.getCreatedNs());
stmt.bindLong(DbOpenHelper.DELAY_UNTIL_NS_COLUMN.columnIndex + 1, jobHolder.getDelayUntilNs());
stmt.bindLong(DbOpenHelper.RUNNING_SESSION_ID_COLUMN.columnIndex + 1, jobHolder.getRunningSessionId());
stmt.bindLong(DbOpenHelper.REQUIRES_NETWORK_COLUMN.columnIndex + 1, jobHolder.requiresNetwork() ? 1L : 0L);
}
/**
* {@inheritDoc}
*/
@Override
public long insertOrReplace(JobHolder jobHolder) {
if (jobHolder.getId() == null) {
return insert(jobHolder);
}
jobHolder.setRunningSessionId(JobManager.NOT_RUNNING_SESSION_ID);
SQLiteStatement stmt = sqlHelper.getInsertOrReplaceStatement();
long id;
synchronized (stmt) {
stmt.clearBindings();
bindValues(stmt, jobHolder);
id = stmt.executeInsert();
}
jobHolder.setId(id);
return id;
}
/**
* {@inheritDoc}
*/
@Override
public void remove(JobHolder jobHolder) {
if (jobHolder.getId() == null) {
JqLog.e("called remove with null job id.");
return;
}
delete(jobHolder.getId());
}
private void delete(Long id) {
SQLiteStatement stmt = sqlHelper.getDeleteStatement();
synchronized (stmt) {
stmt.clearBindings();
stmt.bindLong(1, id);
stmt.execute();
}
}
/**
* {@inheritDoc}
*/
@Override
public int count() {
SQLiteStatement stmt = sqlHelper.getCountStatement();
synchronized (stmt) {
stmt.clearBindings();
stmt.bindLong(1, sessionId);
return (int) stmt.simpleQueryForLong();
}
}
@Override
public int countReadyJobs(boolean hasNetwork, Collection excludeGroups) {
String sql = readyJobsQueryCache.get(hasNetwork, excludeGroups);
if(sql == null) {
String where = createReadyJobWhereSql(hasNetwork, excludeGroups, true);
String subSelect = "SELECT count(*) group_cnt, " + DbOpenHelper.GROUP_ID_COLUMN.columnName
+ " FROM " + DbOpenHelper.JOB_HOLDER_TABLE_NAME
+ " WHERE " + where;
sql = "SELECT SUM(case WHEN " + DbOpenHelper.GROUP_ID_COLUMN.columnName
+ " is null then group_cnt else 1 end) from (" + subSelect + ")";
readyJobsQueryCache.set(sql, hasNetwork, excludeGroups);
}
Cursor cursor = db.rawQuery(sql, new String[]{Long.toString(sessionId), Long.toString(System.nanoTime())});
try {
if(!cursor.moveToNext()) {
return 0;
}
return cursor.getInt(0);
} finally {
cursor.close();
}
}
/**
* {@inheritDoc}
*/
@Override
public JobHolder findJobById(long id) {
Cursor cursor = db.rawQuery(sqlHelper.FIND_BY_ID_QUERY, new String[]{Long.toString(id)});
try {
if(!cursor.moveToFirst()) {
return null;
}
return createJobHolderFromCursor(cursor);
} catch (InvalidBaseJobException e) {
JqLog.e(e, "invalid job on findJobById");
return null;
} finally {
cursor.close();
}
}
/**
* {@inheritDoc}
*/
@Override
public JobHolder nextJobAndIncRunCount(boolean hasNetwork, Collection excludeGroups) {
//we can even keep these prepared but not sure the cost of them in db layer
String selectQuery = nextJobsQueryCache.get(hasNetwork, excludeGroups);
if(selectQuery == null) {
String where = createReadyJobWhereSql(hasNetwork, excludeGroups, false);
selectQuery = sqlHelper.createSelect(
where,
1,
new SqlHelper.Order(DbOpenHelper.PRIORITY_COLUMN, SqlHelper.Order.Type.DESC),
new SqlHelper.Order(DbOpenHelper.CREATED_NS_COLUMN, SqlHelper.Order.Type.ASC),
new SqlHelper.Order(DbOpenHelper.ID_COLUMN, SqlHelper.Order.Type.ASC)
);
nextJobsQueryCache.set(selectQuery, hasNetwork, excludeGroups);
}
Cursor cursor = db.rawQuery(selectQuery, new String[]{Long.toString(sessionId),Long.toString(System.nanoTime())});
try {
if (!cursor.moveToNext()) {
return null;
}
JobHolder holder = createJobHolderFromCursor(cursor);
onJobFetchedForRunning(holder);
return holder;
} catch (InvalidBaseJobException e) {
//delete
Long jobId = cursor.getLong(0);
delete(jobId);
return nextJobAndIncRunCount(true, null);
} finally {
cursor.close();
}
}
private String createReadyJobWhereSql(boolean hasNetwork, Collection excludeGroups, boolean groupByRunningGroup) {
String where = DbOpenHelper.RUNNING_SESSION_ID_COLUMN.columnName + " != ? "
+ " AND " + DbOpenHelper.DELAY_UNTIL_NS_COLUMN.columnName + " <= ? ";
if(hasNetwork == false) {
where += " AND " + DbOpenHelper.REQUIRES_NETWORK_COLUMN.columnName + " != 1 ";
}
String groupConstraint = null;
if(excludeGroups != null && excludeGroups.size() > 0) {
groupConstraint = DbOpenHelper.GROUP_ID_COLUMN.columnName + " IS NULL OR " +
DbOpenHelper.GROUP_ID_COLUMN.columnName + " NOT IN('" + joinStrings("','", excludeGroups) + "')";
}
if(groupByRunningGroup) {
where += " GROUP BY " + DbOpenHelper.GROUP_ID_COLUMN.columnName;
if(groupConstraint != null) {
where += " HAVING " + groupConstraint;
}
} else if(groupConstraint != null) {
where += " AND ( " + groupConstraint + " )";
}
return where;
}
private static String joinStrings(String glue, Collection strings) {
StringBuilder builder = new StringBuilder();
for(String str : strings) {
if(builder.length() != 0) {
builder.append(glue);
}
builder.append(str);
}
return builder.toString();
}
/**
* {@inheritDoc}
*/
@Override
public Long getNextJobDelayUntilNs(boolean hasNetwork) {
SQLiteStatement stmt =
hasNetwork ? sqlHelper.getNextJobDelayedUntilWithNetworkStatement()
: sqlHelper.getNextJobDelayedUntilWithoutNetworkStatement();
synchronized (stmt) {
try {
stmt.clearBindings();
return stmt.simpleQueryForLong();
} catch (SQLiteDoneException e){
return null;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
sqlHelper.truncate();
readyJobsQueryCache.clear();
nextJobsQueryCache.clear();
}
private void onJobFetchedForRunning(JobHolder jobHolder) {
SQLiteStatement stmt = sqlHelper.getOnJobFetchedForRunningStatement();
jobHolder.setRunCount(jobHolder.getRunCount() + 1);
jobHolder.setRunningSessionId(sessionId);
synchronized (stmt) {
stmt.clearBindings();
stmt.bindLong(1, jobHolder.getRunCount());
stmt.bindLong(2, sessionId);
stmt.bindLong(3, jobHolder.getId());
stmt.execute();
}
}
private JobHolder createJobHolderFromCursor(Cursor cursor) throws InvalidBaseJobException {
BaseJob job = safeDeserialize(cursor.getBlob(DbOpenHelper.BASE_JOB_COLUMN.columnIndex));
if (job == null) {
throw new InvalidBaseJobException();
}
return new JobHolder(
cursor.getLong(DbOpenHelper.ID_COLUMN.columnIndex),
cursor.getInt(DbOpenHelper.PRIORITY_COLUMN.columnIndex),
cursor.getString(DbOpenHelper.GROUP_ID_COLUMN.columnIndex),
cursor.getInt(DbOpenHelper.RUN_COUNT_COLUMN.columnIndex),
job,
cursor.getLong(DbOpenHelper.CREATED_NS_COLUMN.columnIndex),
cursor.getLong(DbOpenHelper.DELAY_UNTIL_NS_COLUMN.columnIndex),
cursor.getLong(DbOpenHelper.RUNNING_SESSION_ID_COLUMN.columnIndex)
);
}
private BaseJob safeDeserialize(byte[] bytes) {
try {
return jobSerializer.deserialize(bytes);
} catch (Throwable t) {
JqLog.e(t, "error while deserializing job");
}
return null;
}
private byte[] getSerializeBaseJob(JobHolder jobHolder) {
return safeSerialize(jobHolder.getBaseJob());
}
private byte[] safeSerialize(Object object) {
try {
return jobSerializer.serialize(object);
} catch (Throwable t) {
JqLog.e(t, "error while serializing object %s", object.getClass().getSimpleName());
}
return null;
}
private static class InvalidBaseJobException extends Exception {
}
public static class JavaSerializer implements JobSerializer {
@Override
public byte[] serialize(Object object) throws IOException {
if (object == null) {
return null;
}
ByteArrayOutputStream bos = null;
try {
ObjectOutput out = null;
bos = new ByteArrayOutputStream();
out = new ObjectOutputStream(bos);
out.writeObject(object);
// Get the bytes of the serialized object
return bos.toByteArray();
} finally {
if (bos != null) {
bos.close();
}
}
}
@Override
public T deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
if (bytes == null || bytes.length == 0) {
return null;
}
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return (T) in.readObject();
} finally {
if (in != null) {
in.close();
}
}
}
}
public static interface JobSerializer {
public byte[] serialize(Object object) throws IOException;
public T deserialize(byte[] bytes) throws IOException, ClassNotFoundException;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy