use of com.parse.boltsinternal.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(task -> {
uuid.set(task.getResult());
String[] args = { uuid.get() };
return db.queryAsync(OfflineSQLiteOpenHelper.TABLE_OBJECTS, select, where, args);
}).onSuccess(task -> {
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(task -> {
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((Continuation<String, Task<Void>>) task -> {
String jsonString = task.getResult();
if (jsonString == null) {
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 {
json = new JSONObject(jsonString);
} catch (JSONException e) {
return Task.forError(e);
}
final Map<String, Task<ParseObject>> offlineObjects = new HashMap<>();
(new ParseTraverser() {
@Override
protected boolean visit(Object object1) {
if (object1 instanceof JSONObject && ((JSONObject) object1).optString("__type").equals("OfflineObject")) {
String uuid = ((JSONObject) object1).optString("uuid");
offlineObjects.put(uuid, getPointerAsync(uuid, db));
}
return true;
}
}).setTraverseParseObjects(false).setYieldRoot(false).traverse(json);
return Task.whenAll(offlineObjects.values()).onSuccess(task1 -> {
object.mergeREST(object.getState(), json, new OfflineDecoder(offlineObjects));
return null;
});
}).continueWithTask(task -> {
if (task.isCancelled()) {
tcs.setCancelled();
} else if (task.isFaulted()) {
tcs.setError(task.getError());
} else {
tcs.setResult(object);
}
return tcs.getTask();
});
}
use of com.parse.boltsinternal.Continuation in project Parse-SDK-Android by ParsePlatform.
the class Parse method initialize.
static void initialize(Configuration configuration, ParsePlugins parsePlugins) {
if (isInitialized()) {
PLog.w(TAG, "Parse is already initialized");
return;
}
// NOTE (richardross): We will need this here, as ParsePlugins uses the return value of
// isLocalDataStoreEnabled() to perform additional behavior.
isLocalDatastoreEnabled = configuration.localDataStoreEnabled;
allowCustomObjectId = configuration.allowCustomObjectId;
if (parsePlugins == null) {
ParsePlugins.initialize(configuration.context, configuration);
} else {
ParsePlugins.set(parsePlugins);
}
try {
ParseRESTCommand.server = new URL(configuration.server);
} catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
ParseObject.registerParseSubclasses();
if (configuration.localDataStoreEnabled) {
offlineStore = new OfflineStore(configuration.context);
} else {
ParseKeyValueCache.initialize(configuration.context);
}
// Make sure the data on disk for Parse is for the current
// application.
checkCacheApplicationId();
final Context context = configuration.context;
Task.callInBackground((Callable<Void>) () -> {
getEventuallyQueue(context);
return null;
});
ParseFieldOperations.registerDefaultDecoders();
if (!allParsePushIntentReceiversInternal()) {
throw new SecurityException("To prevent external tampering to your app's notifications, " + "all receivers registered to handle the following actions must have " + "their exported attributes set to false: com.parse.push.intent.RECEIVE, " + "com.parse.push.intent.OPEN, com.parse.push.intent.DELETE");
}
ParseUser.getCurrentUserAsync().makeVoid().continueWith((Continuation<Void, Void>) task -> {
ParseConfig.getCurrentConfig();
return null;
}, Task.BACKGROUND_EXECUTOR);
dispatchOnParseInitialized();
// FYI we probably don't want to do this if we ever add other callbacks.
synchronized (MUTEX_CALLBACKS) {
Parse.callbacks = null;
}
}
use of com.parse.boltsinternal.Continuation in project Parse-SDK-Android by ParsePlatform.
the class ParseRequest method executeAsync.
private Task<Response> executeAsync(final ParseHttpClient client, final ParseHttpRequest request, final int attemptsMade, final long delay, final ProgressCallback downloadProgressCallback, final Task<Void> cancellationToken) {
if (cancellationToken != null && cancellationToken.isCancelled()) {
return Task.cancelled();
}
return sendOneRequestAsync(client, request, downloadProgressCallback).continueWithTask(task -> {
Exception e = task.getError();
if (task.isFaulted() && e instanceof ParseException) {
if (cancellationToken != null && cancellationToken.isCancelled()) {
return Task.cancelled();
}
if (e instanceof ParseRequestException && ((ParseRequestException) e).isPermanentFailure) {
return task;
}
if (attemptsMade < maxRetries()) {
PLog.i("com.parse.ParseRequest", "Request failed. Waiting " + delay + " milliseconds before attempt #" + (attemptsMade + 1));
final TaskCompletionSource<Response> retryTask = new TaskCompletionSource<>();
ParseExecutors.scheduled().schedule(() -> {
executeAsync(client, request, attemptsMade + 1, delay * 2, downloadProgressCallback, cancellationToken).continueWithTask((Continuation<Response, Task<Void>>) task1 -> {
if (task1.isCancelled()) {
retryTask.setCancelled();
} else if (task1.isFaulted()) {
retryTask.setError(task1.getError());
} else {
retryTask.setResult(task1.getResult());
}
return null;
});
}, delay, TimeUnit.MILLISECONDS);
return retryTask.getTask();
}
}
return task;
});
}
use of com.parse.boltsinternal.Continuation in project Parse-SDK-Android by ParsePlatform.
the class ParseUserTest method testSaveEventuallyWhenServerError.
// region testSaveEventuallyWhenServerError
@Test
public void testSaveEventuallyWhenServerError() throws Exception {
Shadows.shadowOf(RuntimeEnvironment.application).grantPermissions(Manifest.permission.ACCESS_NETWORK_STATE);
Parse.Configuration configuration = new Parse.Configuration.Builder(RuntimeEnvironment.application).applicationId(BuildConfig.LIBRARY_PACKAGE_NAME).server("https://api.parse.com/1").enableLocalDataStore().build();
ParsePlugins plugins = ParseTestUtils.mockParsePlugins(configuration);
JSONObject mockResponse = new JSONObject();
mockResponse.put("objectId", "objectId");
mockResponse.put("email", "email@parse.com");
mockResponse.put("username", "username");
mockResponse.put("sessionToken", "r:sessionToken");
mockResponse.put("createdAt", ParseDateFormat.getInstance().format(new Date(1000)));
mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(2000)));
ParseHttpClient restClient = ParseTestUtils.mockParseHttpClientWithResponse(mockResponse, 200, "OK");
when(plugins.restClient()).thenReturn(restClient);
Parse.initialize(configuration, plugins);
ParseUser user = ParseUser.logIn("username", "password");
assertFalse(user.isDirty());
user.put("field", "data");
assertTrue(user.isDirty());
mockResponse = new JSONObject();
mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(3000)));
ParseTestUtils.updateMockParseHttpClientWithResponse(restClient, mockResponse, 200, "OK");
final CountDownLatch saveCountDown1 = new CountDownLatch(1);
final Capture<Exception> exceptionCapture = new Capture<>();
user.saveInBackground().continueWith((Continuation<Void, Void>) task -> {
exceptionCapture.set(task.getError());
saveCountDown1.countDown();
return null;
});
assertTrue(saveCountDown1.await(5, TimeUnit.SECONDS));
assertNull(exceptionCapture.get());
assertFalse(user.isDirty());
user.put("field", "other data");
assertTrue(user.isDirty());
mockResponse = new JSONObject();
mockResponse.put("error", "Save is not allowed");
mockResponse.put("code", 141);
ParseTestUtils.updateMockParseHttpClientWithResponse(restClient, mockResponse, 400, "Bad Request");
final CountDownLatch saveEventuallyCountDown = new CountDownLatch(1);
user.saveEventually().continueWith((Continuation<Void, Void>) task -> {
exceptionCapture.set(task.getError());
saveEventuallyCountDown.countDown();
return null;
});
assertTrue(saveEventuallyCountDown.await(5, TimeUnit.SECONDS));
assertTrue(exceptionCapture.get() instanceof ParseException);
assertEquals(ParseException.SCRIPT_ERROR, ((ParseException) exceptionCapture.get()).getCode());
assertEquals("Save is not allowed", exceptionCapture.get().getMessage());
assertTrue(user.isDirty());
// Simulate reboot
Parse.destroy();
Parse.initialize(configuration, plugins);
user = ParseUser.getCurrentUser();
assertTrue(user.isDirty());
assertEquals("other data", user.get("field"));
user.put("field", "another data");
mockResponse = new JSONObject();
mockResponse.put("updatedAt", ParseDateFormat.getInstance().format(new Date(4000)));
ParseTestUtils.updateMockParseHttpClientWithResponse(restClient, mockResponse, 200, "OK");
final CountDownLatch saveCountDown2 = new CountDownLatch(1);
user.saveInBackground().continueWith((Continuation<Void, Void>) task -> {
exceptionCapture.set(task.getError());
saveCountDown2.countDown();
return null;
});
assertTrue(saveCountDown2.await(5, TimeUnit.SECONDS));
assertNull(exceptionCapture.get());
assertFalse(user.isDirty());
}
use of com.parse.boltsinternal.Continuation in project Parse-SDK-Android by ParsePlatform.
the class ParseRESTObjectBatchCommand method executeBatch.
public static List<Task<JSONObject>> executeBatch(ParseHttpClient client, List<ParseRESTObjectCommand> commands, String sessionToken) {
final int batchSize = commands.size();
List<Task<JSONObject>> tasks = new ArrayList<>(batchSize);
if (batchSize == 1) {
// There's only one, just execute it
tasks.add(commands.get(0).executeAsync(client));
return tasks;
}
if (batchSize > COMMAND_OBJECT_BATCH_MAX_SIZE) {
// There's more than the max, split it up into batches
List<List<ParseRESTObjectCommand>> batches = Lists.partition(commands, COMMAND_OBJECT_BATCH_MAX_SIZE);
for (int i = 0, size = batches.size(); i < size; i++) {
List<ParseRESTObjectCommand> batch = batches.get(i);
tasks.addAll(executeBatch(client, batch, sessionToken));
}
return tasks;
}
final List<TaskCompletionSource<JSONObject>> tcss = new ArrayList<>(batchSize);
for (int i = 0; i < batchSize; i++) {
TaskCompletionSource<JSONObject> tcs = new TaskCompletionSource<>();
tcss.add(tcs);
tasks.add(tcs.getTask());
}
JSONObject parameters = new JSONObject();
JSONArray requests = new JSONArray();
try {
for (ParseRESTObjectCommand command : commands) {
JSONObject requestParameters = new JSONObject();
requestParameters.put("method", command.method.toString());
requestParameters.put("path", new URL(server, command.httpPath).getPath());
JSONObject body = command.jsonParameters;
if (body != null) {
requestParameters.put("body", body);
}
requests.put(requestParameters);
}
parameters.put("requests", requests);
} catch (JSONException | MalformedURLException e) {
throw new RuntimeException(e);
}
ParseRESTCommand command = new ParseRESTObjectBatchCommand("batch", ParseHttpRequest.Method.POST, parameters, sessionToken);
command.executeAsync(client).continueWith((Continuation<JSONObject, Void>) task -> {
TaskCompletionSource<JSONObject> tcs;
if (task.isFaulted() || task.isCancelled()) {
for (int i = 0; i < batchSize; i++) {
tcs = tcss.get(i);
if (task.isFaulted()) {
tcs.setError(task.getError());
} else {
tcs.setCancelled();
}
}
}
JSONObject json = task.getResult();
JSONArray results = json.getJSONArray(KEY_RESULTS);
int resultLength = results.length();
if (resultLength != batchSize) {
for (int i = 0; i < batchSize; i++) {
tcs = tcss.get(i);
tcs.setError(new IllegalStateException("Batch command result count expected: " + batchSize + " but was: " + resultLength));
}
}
for (int i = 0; i < batchSize; i++) {
JSONObject result = results.getJSONObject(i);
tcs = tcss.get(i);
if (result.has("success")) {
JSONObject success = result.getJSONObject("success");
tcs.setResult(success);
} else if (result.has("error")) {
JSONObject error = result.getJSONObject("error");
tcs.setError(new ParseException(error.getInt("code"), error.getString("error")));
}
}
return null;
});
return tasks;
}
Aggregations