use of bolts.Continuation in project Parse-SDK-Android by ParsePlatform.
the class OfflineStore method findAsync.
/**
* Runs a ParseQuery against the store's contents. May cause any instances of T to get fetched
* from the offline database. TODO(klimt): Should the query consider objects that are in memory,
* but not in the offline store?
*
* @param query The query.
* @param user The user making the query.
* @param pin (Optional) The pin we are querying across. If null, all pins.
* @param isCount True if we are doing a count.
* @param db The SQLiteDatabase.
* @param <T> Subclass of ParseObject.
* @return The objects that match the query's constraints.
*/
private <T extends ParseObject> Task<List<T>> findAsync(final ParseQuery.State<T> query, final ParseUser user, final ParsePin pin, final boolean isCount, final ParseSQLiteDatabase db) {
/*
* This is currently unused, but is here to allow future querying across objects that are in the
* process of being deleted eventually.
*/
final boolean includeIsDeletingEventually = false;
final OfflineQueryLogic queryLogic = new OfflineQueryLogic(this);
final List<T> results = new ArrayList<>();
Task<Cursor> queryTask;
if (pin == null) {
String table = OfflineSQLiteOpenHelper.TABLE_OBJECTS;
String[] select = { OfflineSQLiteOpenHelper.KEY_UUID };
String where = OfflineSQLiteOpenHelper.KEY_CLASS_NAME + "=?";
if (!includeIsDeletingEventually) {
where += " AND " + OfflineSQLiteOpenHelper.KEY_IS_DELETING_EVENTUALLY + "=0";
}
String[] args = { query.className() };
queryTask = db.queryAsync(table, select, where, args);
} else {
Task<String> uuidTask = objectToUuidMap.get(pin);
if (uuidTask == null) {
// Pin was never saved locally, therefore there won't be any results.
return Task.forResult(results);
}
queryTask = uuidTask.onSuccessTask(new Continuation<String, Task<Cursor>>() {
@Override
public Task<Cursor> then(Task<String> task) throws Exception {
String uuid = task.getResult();
String table = OfflineSQLiteOpenHelper.TABLE_OBJECTS + " A " + " INNER JOIN " + OfflineSQLiteOpenHelper.TABLE_DEPENDENCIES + " B " + " ON A." + OfflineSQLiteOpenHelper.KEY_UUID + "=B." + OfflineSQLiteOpenHelper.KEY_UUID;
String[] select = { "A." + OfflineSQLiteOpenHelper.KEY_UUID };
String where = OfflineSQLiteOpenHelper.KEY_CLASS_NAME + "=?" + " AND " + OfflineSQLiteOpenHelper.KEY_KEY + "=?";
if (!includeIsDeletingEventually) {
where += " AND " + OfflineSQLiteOpenHelper.KEY_IS_DELETING_EVENTUALLY + "=0";
}
String[] args = { query.className(), uuid };
return db.queryAsync(table, select, where, args);
}
});
}
return queryTask.onSuccessTask(new Continuation<Cursor, Task<Void>>() {
@Override
public Task<Void> then(Task<Cursor> task) throws Exception {
Cursor cursor = task.getResult();
List<String> uuids = new ArrayList<>();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
uuids.add(cursor.getString(0));
}
cursor.close();
// Find objects that match the where clause.
final ConstraintMatcher<T> matcher = queryLogic.createMatcher(query, user);
Task<Void> checkedAllObjects = Task.forResult(null);
for (final String uuid : uuids) {
final Capture<T> object = new Capture<>();
checkedAllObjects = checkedAllObjects.onSuccessTask(new Continuation<Void, Task<T>>() {
@Override
public Task<T> then(Task<Void> task) throws Exception {
return getPointerAsync(uuid, db);
}
}).onSuccessTask(new Continuation<T, Task<T>>() {
@Override
public Task<T> then(Task<T> task) throws Exception {
object.set(task.getResult());
return fetchLocallyAsync(object.get(), db);
}
}).onSuccessTask(new Continuation<T, Task<Boolean>>() {
@Override
public Task<Boolean> then(Task<T> task) throws Exception {
if (!object.get().isDataAvailable()) {
return Task.forResult(false);
}
return matcher.matchesAsync(object.get(), db);
}
}).onSuccess(new Continuation<Boolean, Void>() {
@Override
public Void then(Task<Boolean> task) {
if (task.getResult()) {
results.add(object.get());
}
return null;
}
});
}
return checkedAllObjects;
}
}).onSuccessTask(new Continuation<Void, Task<List<T>>>() {
@Override
public Task<List<T>> then(Task<Void> task) throws Exception {
// Sort by any sort operators.
OfflineQueryLogic.sort(results, query);
// Apply the skip.
List<T> trimmedResults = results;
int skip = query.skip();
if (!isCount && skip >= 0) {
skip = Math.min(query.skip(), trimmedResults.size());
trimmedResults = trimmedResults.subList(skip, trimmedResults.size());
}
// Trim to the limit.
int limit = query.limit();
if (!isCount && limit >= 0 && trimmedResults.size() > limit) {
trimmedResults = trimmedResults.subList(0, limit);
}
// Fetch the includes.
Task<Void> fetchedIncludesTask = Task.forResult(null);
for (final T object : trimmedResults) {
fetchedIncludesTask = fetchedIncludesTask.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
return OfflineQueryLogic.fetchIncludesAsync(OfflineStore.this, object, query, db);
}
});
}
final List<T> finalTrimmedResults = trimmedResults;
return fetchedIncludesTask.onSuccess(new Continuation<Void, List<T>>() {
@Override
public List<T> then(Task<Void> task) throws Exception {
return finalTrimmedResults;
}
});
}
});
}
use of bolts.Continuation in project Parse-SDK-Android by ParsePlatform.
the class OfflineStore method fetchLocallyAsync.
/**
* Gets the data for the given object from the offline database. Returns a task that will be
* completed if data for the object was available. If the object is not in the cache, the task
* will be faulted, with a CACHE_MISS error.
*
* @param object
* The object to fetch.
* @param db
* A database connection to use.
*/
/* package for OfflineQueryLogic */
<T extends ParseObject> Task<T> fetchLocallyAsync(final T object, final ParseSQLiteDatabase db) {
final TaskCompletionSource<T> tcs = new TaskCompletionSource<>();
Task<String> uuidTask;
synchronized (lock) {
if (fetchedObjects.containsKey(object)) {
//noinspection unchecked
return (Task<T>) fetchedObjects.get(object);
}
/*
* Put a placeholder so that anyone else who attempts to fetch this object will just wait for
* this call to finish doing it.
*/
//noinspection unchecked
fetchedObjects.put(object, (Task<ParseObject>) tcs.getTask());
uuidTask = objectToUuidMap.get(object);
}
String className = object.getClassName();
String objectId = object.getObjectId();
/*
* If this gets set, then it will contain data from the offline store that needs to be merged
* into the existing object in memory.
*/
Task<String> jsonStringTask = Task.forResult(null);
if (objectId == null) {
// This Object has never been saved to Parse.
if (uuidTask == null) {
/*
* This object was not pulled from the data store or previously saved to it, so there's
* nothing that can be fetched from it. This isn't an error, because it's really convenient
* to try to fetch objects from the offline store just to make sure they are up-to-date, and
* we shouldn't force developers to specially handle this case.
*/
} else {
/*
* This object is a new ParseObject that is known to the data store, but hasn't been
* fetched. The only way this could happen is if the object had previously been stored in
* the offline store, then the object was removed from memory (maybe by rebooting), and then
* a object with a pointer to it was fetched, so we only created the pointer. We need to
* pull the data out of the database using the UUID.
*/
final String[] select = { OfflineSQLiteOpenHelper.KEY_JSON };
final String where = OfflineSQLiteOpenHelper.KEY_UUID + " = ?";
final Capture<String> uuid = new Capture<>();
jsonStringTask = uuidTask.onSuccessTask(new Continuation<String, Task<Cursor>>() {
@Override
public Task<Cursor> then(Task<String> task) throws Exception {
uuid.set(task.getResult());
String[] args = { uuid.get() };
return db.queryAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, select, where, args);
}
}).onSuccess(new Continuation<Cursor, String>() {
@Override
public String then(Task<Cursor> task) throws Exception {
Cursor cursor = task.getResult();
cursor.moveToFirst();
if (cursor.isAfterLast()) {
cursor.close();
throw new IllegalStateException("Attempted to find non-existent uuid " + uuid.get());
}
String json = cursor.getString(0);
cursor.close();
return json;
}
});
}
} else {
if (uuidTask != null) {
/*
* This object is an existing ParseObject, and we must've already pulled its data out of the
* offline store, or else we wouldn't know its UUID. This should never happen.
*/
tcs.setError(new IllegalStateException("This object must have already been " + "fetched from the local datastore, but isn't marked as fetched."));
synchronized (lock) {
// Forget we even tried to fetch this object, so that retries will actually... retry.
fetchedObjects.remove(object);
}
return tcs.getTask();
}
/*
* We've got a pointer to an existing ParseObject, but we've never pulled its data out of the
* offline store. Since fetching from the server forces a fetch from the offline store, that
* means this is a pointer. We need to try to find any existing entry for this object in the
* database.
*/
String[] select = { OfflineSQLiteOpenHelper.KEY_JSON, OfflineSQLiteOpenHelper.KEY_UUID };
String where = String.format("%s = ? AND %s = ?", OfflineSQLiteOpenHelper.KEY_CLASS_NAME, OfflineSQLiteOpenHelper.KEY_OBJECT_ID);
String[] args = { className, objectId };
jsonStringTask = db.queryAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, select, where, args).onSuccess(new Continuation<Cursor, String>() {
@Override
public String then(Task<Cursor> task) throws Exception {
Cursor cursor = task.getResult();
cursor.moveToFirst();
if (cursor.isAfterLast()) {
/*
* This is a pointer that came from Parse that references an object that has
* never been saved in the offline store before. This just means there's no data
* in the store that needs to be merged into the object.
*/
cursor.close();
throw new ParseException(ParseException.CACHE_MISS, "This object is not available in the offline cache.");
}
// we should fetch its data and record its UUID for future reference.
String jsonString = cursor.getString(0);
String newUUID = cursor.getString(1);
cursor.close();
synchronized (lock) {
/*
* It's okay to put this object into the uuid map. No one will try to fetch
* it, because it's already in the fetchedObjects map. And no one will try to
* save to it without fetching it first, so everything should be just fine.
*/
objectToUuidMap.put(object, Task.forResult(newUUID));
uuidToObjectMap.put(newUUID, object);
}
return jsonString;
}
});
}
return jsonStringTask.onSuccessTask(new Continuation<String, Task<Void>>() {
@Override
public Task<Void> then(Task<String> task) throws Exception {
String jsonString = task.getResult();
if (jsonString == null) {
/*
* This means we tried to fetch an object from the database that was never actually saved
* locally. This probably means that its parent object was saved locally and we just
* created a pointer to this object. This should be considered a cache miss.
*/
return Task.forError(new ParseException(ParseException.CACHE_MISS, "Attempted to fetch an object offline which was never saved to the offline cache."));
}
final JSONObject json;
try {
/*
* We can assume that whatever is in the database is the last known server state. The only
* things to maintain from the in-memory object are any changes since the object was last
* put in the database.
*/
json = new JSONObject(jsonString);
} catch (JSONException e) {
return Task.forError(e);
}
// Fetch all the offline objects before we decode.
final Map<String, Task<ParseObject>> offlineObjects = new HashMap<>();
(new ParseTraverser() {
@Override
protected boolean visit(Object object) {
if (object instanceof JSONObject && ((JSONObject) object).optString("__type").equals("OfflineObject")) {
String uuid = ((JSONObject) object).optString("uuid");
offlineObjects.put(uuid, getPointerAsync(uuid, db));
}
return true;
}
}).setTraverseParseObjects(false).setYieldRoot(false).traverse(json);
return Task.whenAll(offlineObjects.values()).onSuccess(new Continuation<Void, Void>() {
@Override
public Void then(Task<Void> task) throws Exception {
object.mergeREST(object.getState(), json, new OfflineDecoder(offlineObjects));
return null;
}
});
}
}).continueWithTask(new Continuation<Void, Task<T>>() {
@Override
public Task<T> then(Task<Void> task) throws Exception {
if (task.isCancelled()) {
tcs.setCancelled();
} else if (task.isFaulted()) {
tcs.setError(task.getError());
} else {
tcs.setResult(object);
}
return tcs.getTask();
}
});
}
use of bolts.Continuation in project Parse-SDK-Android by ParsePlatform.
the class NetworkQueryController method countAsync.
/* package */
<T extends ParseObject> Task<Integer> countAsync(final ParseQuery.State<T> state, String sessionToken, boolean shouldRetry, Task<Void> ct) {
final ParseRESTCommand command = ParseRESTQueryCommand.countCommand(state, sessionToken);
if (shouldRetry) {
command.enableRetrying();
}
return command.executeAsync(restClient, ct).onSuccessTask(new Continuation<JSONObject, Task<JSONObject>>() {
@Override
public Task<JSONObject> then(Task<JSONObject> task) throws Exception {
// Cache the results, unless we are ignoring the cache
ParseQuery.CachePolicy policy = state.cachePolicy();
if (policy != null && policy != ParseQuery.CachePolicy.IGNORE_CACHE) {
JSONObject result = task.getResult();
ParseKeyValueCache.saveToKeyValueCache(command.getCacheKey(), result.toString());
}
return task;
}
}, Task.BACKGROUND_EXECUTOR).onSuccess(new Continuation<JSONObject, Integer>() {
@Override
public Integer then(Task<JSONObject> task) throws Exception {
// Convert response
return task.getResult().optInt("count");
}
});
}
use of bolts.Continuation in project Parse-SDK-Android by ParsePlatform.
the class OfflineQueryLogic method createOrMatcher.
/**
* Handles $or queries.
*/
private <T extends ParseObject> ConstraintMatcher<T> createOrMatcher(ParseUser user, ArrayList<QueryConstraints> queries) {
// Make a list of all the matchers to OR together.
final ArrayList<ConstraintMatcher<T>> matchers = new ArrayList<>();
for (QueryConstraints constraints : queries) {
ConstraintMatcher<T> matcher = createMatcher(user, constraints);
matchers.add(matcher);
}
/*
* Now OR together the constraints for each query.
*/
return new ConstraintMatcher<T>(user) {
@Override
public Task<Boolean> matchesAsync(final T object, final ParseSQLiteDatabase db) {
Task<Boolean> task = Task.forResult(false);
for (final ConstraintMatcher<T> matcher : matchers) {
task = task.onSuccessTask(new Continuation<Boolean, Task<Boolean>>() {
@Override
public Task<Boolean> then(Task<Boolean> task) throws Exception {
if (task.getResult()) {
return task;
}
return matcher.matchesAsync(object, db);
}
});
}
return task;
}
};
}
use of bolts.Continuation in project Parse-SDK-Android by ParsePlatform.
the class OfflineQueryLogic method fetchIncludeAsync.
/**
* Makes sure that the object specified by path, relative to container, is fetched.
*/
private static Task<Void> fetchIncludeAsync(final OfflineStore store, final Object container, final String path, final ParseSQLiteDatabase db) throws ParseException {
// If there's no object to include, that's fine.
if (container == null) {
return Task.forResult(null);
}
// If the container is a list or array, fetch all the sub-items.
if (container instanceof Collection) {
Collection<?> collection = (Collection<?>) container;
// We do the fetches in series because it makes it easier to fail on the first error.
Task<Void> task = Task.forResult(null);
for (final Object item : collection) {
task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
return fetchIncludeAsync(store, item, path, db);
}
});
}
return task;
} else if (container instanceof JSONArray) {
final JSONArray array = (JSONArray) container;
// We do the fetches in series because it makes it easier to fail on the first error.
Task<Void> task = Task.forResult(null);
for (int i = 0; i < array.length(); ++i) {
final int index = i;
task = task.onSuccessTask(new Continuation<Void, Task<Void>>() {
@Override
public Task<Void> then(Task<Void> task) throws Exception {
return fetchIncludeAsync(store, array.get(index), path, db);
}
});
}
return task;
}
// If we've reached the end of the path, then actually do the fetch.
if (path == null) {
if (JSONObject.NULL.equals(container)) {
// throwing an exception.
return Task.forResult(null);
} else if (container instanceof ParseObject) {
ParseObject object = (ParseObject) container;
return store.fetchLocallyAsync(object, db).makeVoid();
} else {
return Task.forError(new ParseException(ParseException.INVALID_NESTED_KEY, "include is invalid for non-ParseObjects"));
}
}
// Descend into the container and try again.
String[] parts = path.split("\\.", 2);
final String key = parts[0];
final String rest = (parts.length > 1 ? parts[1] : null);
// Make sure the container is fetched.
return Task.<Void>forResult(null).continueWithTask(new Continuation<Void, Task<Object>>() {
@Override
public Task<Object> then(Task<Void> task) throws Exception {
if (container instanceof ParseObject) {
// Make sure this object is fetched before descending into it.
return fetchIncludeAsync(store, container, null, db).onSuccess(new Continuation<Void, Object>() {
@Override
public Object then(Task<Void> task) throws Exception {
return ((ParseObject) container).get(key);
}
});
} else if (container instanceof Map) {
return Task.forResult(((Map) container).get(key));
} else if (container instanceof JSONObject) {
return Task.forResult(((JSONObject) container).opt(key));
} else if (JSONObject.NULL.equals(container)) {
// throwing an exception.
return null;
} else {
return Task.forError(new IllegalStateException("include is invalid"));
}
}
}).onSuccessTask(new Continuation<Object, Task<Void>>() {
@Override
public Task<Void> then(Task<Object> task) throws Exception {
return fetchIncludeAsync(store, task.getResult(), rest, db);
}
});
}
Aggregations