Search in sources :

Example 1 with Continuation

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;
                }
            });
        }
    });
}
Also used : Task(bolts.Task) ArrayList(java.util.ArrayList) Cursor(android.database.Cursor) Capture(bolts.Capture) ArrayList(java.util.ArrayList) List(java.util.List) LinkedList(java.util.LinkedList) Continuation(bolts.Continuation) JSONException(org.json.JSONException)

Example 2 with Continuation

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();
        }
    });
}
Also used : Task(bolts.Task) HashMap(java.util.HashMap) WeakHashMap(java.util.WeakHashMap) Cursor(android.database.Cursor) Capture(bolts.Capture) TaskCompletionSource(bolts.TaskCompletionSource) Continuation(bolts.Continuation) JSONException(org.json.JSONException) JSONException(org.json.JSONException) JSONObject(org.json.JSONObject) JSONObject(org.json.JSONObject)

Example 3 with Continuation

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");
        }
    });
}
Also used : Continuation(bolts.Continuation) Task(bolts.Task) JSONObject(org.json.JSONObject) JSONException(org.json.JSONException)

Example 4 with Continuation

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;
        }
    };
}
Also used : Continuation(bolts.Continuation) Task(bolts.Task) ArrayList(java.util.ArrayList) QueryConstraints(com.parse.ParseQuery.QueryConstraints)

Example 5 with Continuation

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);
        }
    });
}
Also used : Continuation(bolts.Continuation) Task(bolts.Task) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) JSONObject(org.json.JSONObject) Collection(java.util.Collection) JSONObject(org.json.JSONObject) HashMap(java.util.HashMap) Map(java.util.Map)

Aggregations

Continuation (bolts.Continuation)22 Task (bolts.Task)22 JSONException (org.json.JSONException)9 ArrayList (java.util.ArrayList)8 JSONObject (org.json.JSONObject)8 CancellationException (java.util.concurrent.CancellationException)6 AggregateException (bolts.AggregateException)4 Capture (bolts.Capture)4 IOException (java.io.IOException)4 TaskCompletionSource (bolts.TaskCompletionSource)3 File (java.io.File)3 Callable (java.util.concurrent.Callable)3 ContentValues (android.content.ContentValues)2 Cursor (android.database.Cursor)2 QueryConstraints (com.parse.ParseQuery.QueryConstraints)2 FileNotFoundException (java.io.FileNotFoundException)2 UnsupportedEncodingException (java.io.UnsupportedEncodingException)2 HashMap (java.util.HashMap)2 LinkedList (java.util.LinkedList)2 List (java.util.List)2