GitHub PullRequestReviewCommentEvent at 2014-12-16 19:18:38
Repository: commcare-odk
Actor: orangejenny
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/dimagi/commcare-odk/pulls/comments/21923691","id":21923691,"diff_hunk":"@@ -3,62 +3,241 @@\n  */\r\n package org.commcare.android.models;\r\n \r\n+import java.util.Enumeration;\r\n+import java.util.Hashtable;\r\n+\r\n+import net.sqlcipher.database.SQLiteDatabase;\r\n+\r\n+import org.commcare.android.database.user.models.EntityStorageCache;\r\n+import org.commcare.android.util.StringUtils;\r\n+import org.commcare.dalvik.application.CommCareApplication;\r\n import org.commcare.suite.model.DetailField;\r\n import org.commcare.suite.model.Text;\r\n import org.javarosa.core.model.condition.EvaluationContext;\r\n+import org.javarosa.core.model.instance.TreeReference;\r\n import org.javarosa.xpath.XPathException;\r\n+import org.javarosa.xpath.expr.XPathExpression;\r\n+import org.javarosa.xpath.expr.XPathFuncExpr;\r\n \r\n \r\n \r\n /**\r\n+ * An AsyncEntity is an entity reference which is capable of building its\r\n+ * values (evaluating all Text elements/background data elements) lazily\r\n+ * rather than upfront when the entity is constructed.\r\n+ * \r\n+ * It is threadsafe.\r\n+ * \r\n+ * It will attempt to Cache its values persistently by a derived entity key rather\r\n+ * than evaluating them each time when possible. This can be slow to perform across\r\n+ * all entities internally due to the overhead of establishing the db connection, it\r\n+ * is recommended that the entities be primed externally with a bulk query.\r\n+ * \r\n  * @author ctsims\r\n  *\r\n  */\r\n-public class AsyncEntity<T> extends Entity<T>{\r\n+public class AsyncEntity extends Entity<TreeReference>{\r\n+    \r\n+    boolean caching = true;\r\n     \r\n     DetailField[] fields;\r\n     Object[] data;\r\n-    String[] sortData;\r\n+    private String[] sortData;\r\n+    private String[][] sortDataPieces;\r\n     EvaluationContext context;\r\n+    Hashtable<String, XPathExpression> mVariableDeclarations;\r\n+    boolean mVariableContextLoaded = false;\r\n+    String mCacheIndex;\r\n+    String mDetailId;\r\n+\r\n+    private EntityStorageCache mEntityStorageCache;\r\n+    \r\n+    /*\r\n+     * the Object's lock. NOTE: _DO NOT LOCK ANY CODE WHICH READS/WRITES THE CACHE\r\n+     * UNTIL YOU HAVE A LOCK FOR THE DB!\r\n+     * \r\n+     * The lock is for the integrity of this object, not the larger environment, \r\n+     * and any DB access has its own implict lock between threads, so it's easy\r\n+     * to accidentally deadlock if you don't already have the db lock\r\n+     * \r\n+     * Basically you should never be calling mEntityStorageCache from inside of\r\n+     * a lock that \r\n+     */\r\n+    private Object mAsyncLock = new Object();\r\n     \r\n-    public AsyncEntity(DetailField[] fields, EvaluationContext ec, T t) {\r\n+    public AsyncEntity(DetailField[] fields, EvaluationContext ec, TreeReference t, Hashtable<String, XPathExpression> variables, EntityStorageCache cache, String cacheIndex, String detailId) {\r\n         super(t);\r\n         this.fields = fields;\r\n         this.data = new Object[fields.length];\r\n         this.sortData = new String[fields.length];\r\n+        this.sortDataPieces = new String[fields.length][];\r\n         this.context = ec;\r\n+        this.mVariableDeclarations = variables;\r\n+        this.mEntityStorageCache = cache;\r\n+        \r\n+        //TODO: It's weird that we pass this in, kind of, but the thing is that we don't want to figure out\r\n+        //if this ref is _cachable_ every time, since it's a pretty big lift\r\n+        this.mCacheIndex = cacheIndex;\r\n+        \r\n+        this.mDetailId = detailId;\r\n+    }\r\n+    \r\n+    public String getEntityCacheIndex() {\r\n+        return mCacheIndex;\r\n+    }\r\n+    \r\n+    private void loadVariableContext() {\r\n+        synchronized(mAsyncLock) {\r\n+            if(!mVariableContextLoaded) {\r\n+                //These are actually in an ordered hashtable, so we can't just get the keyset, since it's\r\n+                //in a 1.3 hashtable equivalent\r\n+                for(Enumeration<String> en = mVariableDeclarations.keys(); en.hasMoreElements();) {\r\n+                    String key = en.nextElement();\r\n+                    context.setVariable(key, XPathFuncExpr.unpack(mVariableDeclarations.get(key).eval(context)));\r\n+                }\r\n+                mVariableContextLoaded = true;\r\n+            }\r\n+        }\r\n     }\r\n     \r\n     public Object getField(int i) {\r\n-        if(data[i] == null) {\r\n-            try {\r\n-                data[i] = fields[i].getTemplate().evaluate(context);\r\n-            } catch(XPathException xpe) {\r\n-                xpe.printStackTrace();\r\n-                data[i] = \"<invalid xpath: \" + xpe.getMessage() + \">\";\r\n+        synchronized(mAsyncLock) {\r\n+            loadVariableContext();\r\n+            if(data[i] == null) {\r\n+                try {\r\n+                    data[i] = fields[i].getTemplate().evaluate(context);\r\n+                } catch(XPathException xpe) {\r\n+                    xpe.printStackTrace();\r\n+                    data[i] = \"<invalid xpath: \" + xpe.getMessage() + \">\";\r\n+                }\r\n             }\r\n+            return data[i];\r\n         }\r\n-        return data[i];\r\n+    }\r\n+    \r\n+    /**\r\n+     * Gets the indexed field used for searching and sorting these entities \r\n+     * \r\n+     * @return either the sort or the string field at the provided index, normalized\r\n+     * (IE: lowercase, etc) for sorting and searching.\r\n+     */\r\n+    public String getNormalizedField(int i) {\r\n+        String normalized = this.getSortField(i);\r\n+        if(normalized == null) { return \"\"; }\r\n+        return normalized;\r\n     }\r\n     \r\n     public String getSortField(int i) {\r\n-        if(sortData[i] == null) {\r\n-            try {\r\n-                Text sortText = fields[i].getSort();\r\n-                if(sortText == null) {\r\n-                    sortData[i] = getFieldString(i);\r\n-                } else {\r\n-                    sortData[i] = sortText.evaluate(context);\r\n+        //Get a db handle so we can get an outer lock\r\n+        SQLiteDatabase db = CommCareApplication._().getUserDbHandle();\r\n+        \r\n+        //get the db lock\r\n+        db.beginTransaction();\r\n+        try {\r\n+            //get our second lock.\r\n+            synchronized(mAsyncLock) {\r\n+                if(sortData[i] == null) {\r\n+                    Text sortText = fields[i].getSort();\r\n+                    if(sortText == null) {\r\n+                        db.setTransactionSuccessful();\r\n+                        return null;\r\n+                    }\r\n+                        \r\n+                    String cacheKey = EntityStorageCache.getCacheKey(mDetailId,String.valueOf(i)); \r\n+                    \r\n+                    if(mCacheIndex != null) {\r\n+                        //Check the cache!\r\n+                        String value = mEntityStorageCache.retrieveCacheValue(mCacheIndex, cacheKey);\r\n+                        if(value != null) {\r\n+                            this.setSortData(i, value);\r\n+                            db.setTransactionSuccessful();\r\n+                            return sortData[i];\r\n+                        }\r\n+                    }\r\n+                    \r\n+                    \r\n+                    loadVariableContext();\r\n+                    try {\r\n+                        sortText = fields[i].getSort();\r\n+                        if(sortText == null) {\r\n+                            this.setSortData(i, getFieldString(i));\r\n+                        } else {\r\n+                            this.setSortData(i, StringUtils.normalize(sortText.evaluate(context)));\r\n+                        }\r\n+                        \r\n+                        mEntityStorageCache.cache(mCacheIndex, cacheKey, sortData[i]);\r\n+                    } catch(XPathException xpe) {\r\n+                        xpe.printStackTrace();\r\n+                        sortData[i] = \"<invalid xpath: \" + xpe.getMessage() + \">\";\r\n+                    }\r\n                 }\r\n-            } catch(XPathException xpe) {\r\n-                xpe.printStackTrace();\r\n-                sortData[i] = \"<invalid xpath: \" + xpe.getMessage() + \">\";\r\n+                db.setTransactionSuccessful();\r\n+                return sortData[i];\r\n             }\r\n+        \r\n+        } finally {\r\n+            //free the db lock.\r\n+            db.endTransaction();\r\n         }\r\n-        return sortData[i];\r\n     }\r\n \r\n     public int getNumFields() {\r\n         return fields.length;\r\n     }\r\n+    \r\n+    /**\r\n+     * @param i index of field\r\n+     * @return True iff the given field is relevant and has a non-blank value.\r\n+     */\r\n+    public boolean isValidField(int i) {\r\n+        //FIX THIS\r\n+        return true;\r\n+    }\r\n+    \r\n+    public TreeReference getElement() {\r","path":"app/src/org/commcare/android/models/AsyncEntity.java","position":216,"original_position":216,"commit_id":"8c79bf60f82ab83db8cdc0f23db4a32e8b564dc7","original_commit_id":"8c79bf60f82ab83db8cdc0f23db4a32e8b564dc7","user":{"login":"orangejenny","id":1486591,"avatar_url":"https://avatars.githubusercontent.com/u/1486591?v=3","gravatar_id":"","url":"https://api.github.com/users/orangejenny","html_url":"https://github.com/orangejenny","followers_url":"https://api.github.com/users/orangejenny/followers","following_url":"https://api.github.com/users/orangejenny/following{/other_user}","gists_url":"https://api.github.com/users/orangejenny/gists{/gist_id}","starred_url":"https://api.github.com/users/orangejenny/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/orangejenny/subscriptions","organizations_url":"https://api.github.com/users/orangejenny/orgs","repos_url":"https://api.github.com/users/orangejenny/repos","events_url":"https://api.github.com/users/orangejenny/events{/privacy}","received_events_url":"https://api.github.com/users/orangejenny/received_events","type":"User","site_admin":false},"body":"Is this needed? Why doesn't falling back on Entity's getElement work?","created_at":"2014-12-16T19:18:38Z","updated_at":"2014-12-16T19:18:38Z","html_url":"https://github.com/dimagi/commcare-odk/pull/83#discussion_r21923691","pull_request_url":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83","_links":{"self":{"href":"https://api.github.com/repos/dimagi/commcare-odk/pulls/comments/21923691"},"html":{"href":"https://github.com/dimagi/commcare-odk/pull/83#discussion_r21923691"},"pull_request":{"href":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83"}}},"pull_request":{"url":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83","id":24968629,"html_url":"https://github.com/dimagi/commcare-odk/pull/83","diff_url":"https://github.com/dimagi/commcare-odk/pull/83.diff","patch_url":"https://github.com/dimagi/commcare-odk/pull/83.patch","issue_url":"https://api.github.com/repos/dimagi/commcare-odk/issues/83","number":83,"state":"open","locked":false,"title":"Asynchronous Cached Entities","user":{"login":"ctsims","id":155066,"avatar_url":"https://avatars.githubusercontent.com/u/155066?v=3","gravatar_id":"","url":"https://api.github.com/users/ctsims","html_url":"https://github.com/ctsims","followers_url":"https://api.github.com/users/ctsims/followers","following_url":"https://api.github.com/users/ctsims/following{/other_user}","gists_url":"https://api.github.com/users/ctsims/gists{/gist_id}","starred_url":"https://api.github.com/users/ctsims/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ctsims/subscriptions","organizations_url":"https://api.github.com/users/ctsims/orgs","repos_url":"https://api.github.com/users/ctsims/repos","events_url":"https://api.github.com/users/ctsims/events{/privacy}","received_events_url":"https://api.github.com/users/ctsims/received_events","type":"User","site_admin":false},"body":"Implementation of entity caching for flagged indexes on detail lists. \r\n\r\nFor valid <detail> definitions, instead of a synchronous entity loader, an asynchronous factory is used which does not require loading the full entity. The indexed properties (those available for searching and sorting) are evaluated in a lazy manner. \r\n\r\nAdditionally, the properties are cached where valid, and that property cache is loaded upon request in order to prime the lazy nodes. \r\n\r\nSearching on the entity screen is also now asynchronous to allow users to type long strings without facing the delay of performing the search across the entity set.\r\n\r\nX-Requested: CommCare, JavaRosa\r\n\r\nRelies-on: inline_entity_tiles, feature_flags\r\n\r\nNOTE: This update also involves updating our version of the SQLCipher library, which will introduce the need to swap the binary libs back and forth when going between 2.17 and 2.16-.","created_at":"2014-11-24T22:18:06Z","updated_at":"2014-12-16T19:18:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bb53749bc964dacad3b86b1a2d744c430fcf1f88","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83/commits","review_comments_url":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83/comments","review_comment_url":"https://api.github.com/repos/dimagi/commcare-odk/pulls/comments/{number}","comments_url":"https://api.github.com/repos/dimagi/commcare-odk/issues/83/comments","statuses_url":"https://api.github.com/repos/dimagi/commcare-odk/statuses/8c79bf60f82ab83db8cdc0f23db4a32e8b564dc7","head":{"label":"dimagi:async_entities","ref":"async_entities","sha":"8c79bf60f82ab83db8cdc0f23db4a32e8b564dc7","user":{"login":"dimagi","id":94514,"avatar_url":"https://avatars.githubusercontent.com/u/94514?v=3","gravatar_id":"","url":"https://api.github.com/users/dimagi","html_url":"https://github.com/dimagi","followers_url":"https://api.github.com/users/dimagi/followers","following_url":"https://api.github.com/users/dimagi/following{/other_user}","gists_url":"https://api.github.com/users/dimagi/gists{/gist_id}","starred_url":"https://api.github.com/users/dimagi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dimagi/subscriptions","organizations_url":"https://api.github.com/users/dimagi/orgs","repos_url":"https://api.github.com/users/dimagi/repos","events_url":"https://api.github.com/users/dimagi/events{/privacy}","received_events_url":"https://api.github.com/users/dimagi/received_events","type":"Organization","site_admin":false},"repo":{"id":20939877,"name":"commcare-odk","full_name":"dimagi/commcare-odk","owner":{"login":"dimagi","id":94514,"avatar_url":"https://avatars.githubusercontent.com/u/94514?v=3","gravatar_id":"","url":"https://api.github.com/users/dimagi","html_url":"https://github.com/dimagi","followers_url":"https://api.github.com/users/dimagi/followers","following_url":"https://api.github.com/users/dimagi/following{/other_user}","gists_url":"https://api.github.com/users/dimagi/gists{/gist_id}","starred_url":"https://api.github.com/users/dimagi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dimagi/subscriptions","organizations_url":"https://api.github.com/users/dimagi/orgs","repos_url":"https://api.github.com/users/dimagi/repos","events_url":"https://api.github.com/users/dimagi/events{/privacy}","received_events_url":"https://api.github.com/users/dimagi/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dimagi/commcare-odk","description":"","fork":false,"url":"https://api.github.com/repos/dimagi/commcare-odk","forks_url":"https://api.github.com/repos/dimagi/commcare-odk/forks","keys_url":"https://api.github.com/repos/dimagi/commcare-odk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dimagi/commcare-odk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dimagi/commcare-odk/teams","hooks_url":"https://api.github.com/repos/dimagi/commcare-odk/hooks","issue_events_url":"https://api.github.com/repos/dimagi/commcare-odk/issues/events{/number}","events_url":"https://api.github.com/repos/dimagi/commcare-odk/events","assignees_url":"https://api.github.com/repos/dimagi/commcare-odk/assignees{/user}","branches_url":"https://api.github.com/repos/dimagi/commcare-odk/branches{/branch}","tags_url":"https://api.github.com/repos/dimagi/commcare-odk/tags","blobs_url":"https://api.github.com/repos/dimagi/commcare-odk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dimagi/commcare-odk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dimagi/commcare-odk/git/refs{/sha}","trees_url":"https://api.github.com/repos/dimagi/commcare-odk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dimagi/commcare-odk/statuses/{sha}","languages_url":"https://api.github.com/repos/dimagi/commcare-odk/languages","stargazers_url":"https://api.github.com/repos/dimagi/commcare-odk/stargazers","contributors_url":"https://api.github.com/repos/dimagi/commcare-odk/contributors","subscribers_url":"https://api.github.com/repos/dimagi/commcare-odk/subscribers","subscription_url":"https://api.github.com/repos/dimagi/commcare-odk/subscription","commits_url":"https://api.github.com/repos/dimagi/commcare-odk/commits{/sha}","git_commits_url":"https://api.github.com/repos/dimagi/commcare-odk/git/commits{/sha}","comments_url":"https://api.github.com/repos/dimagi/commcare-odk/comments{/number}","issue_comment_url":"https://api.github.com/repos/dimagi/commcare-odk/issues/comments/{number}","contents_url":"https://api.github.com/repos/dimagi/commcare-odk/contents/{+path}","compare_url":"https://api.github.com/repos/dimagi/commcare-odk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dimagi/commcare-odk/merges","archive_url":"https://api.github.com/repos/dimagi/commcare-odk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dimagi/commcare-odk/downloads","issues_url":"https://api.github.com/repos/dimagi/commcare-odk/issues{/number}","pulls_url":"https://api.github.com/repos/dimagi/commcare-odk/pulls{/number}","milestones_url":"https://api.github.com/repos/dimagi/commcare-odk/milestones{/number}","notifications_url":"https://api.github.com/repos/dimagi/commcare-odk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dimagi/commcare-odk/labels{/name}","releases_url":"https://api.github.com/repos/dimagi/commcare-odk/releases{/id}","created_at":"2014-06-17T21:17:26Z","updated_at":"2014-12-16T17:49:41Z","pushed_at":"2014-12-16T17:49:43Z","git_url":"git://github.com/dimagi/commcare-odk.git","ssh_url":"git@github.com:dimagi/commcare-odk.git","clone_url":"https://github.com/dimagi/commcare-odk.git","svn_url":"https://github.com/dimagi/commcare-odk","homepage":null,"size":28636,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":6,"forks":4,"open_issues":6,"watchers":1,"default_branch":"master"}},"base":{"label":"dimagi:master","ref":"master","sha":"c605141a0a9f3acbaa7fef1593e9f5f7a0d0e627","user":{"login":"dimagi","id":94514,"avatar_url":"https://avatars.githubusercontent.com/u/94514?v=3","gravatar_id":"","url":"https://api.github.com/users/dimagi","html_url":"https://github.com/dimagi","followers_url":"https://api.github.com/users/dimagi/followers","following_url":"https://api.github.com/users/dimagi/following{/other_user}","gists_url":"https://api.github.com/users/dimagi/gists{/gist_id}","starred_url":"https://api.github.com/users/dimagi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dimagi/subscriptions","organizations_url":"https://api.github.com/users/dimagi/orgs","repos_url":"https://api.github.com/users/dimagi/repos","events_url":"https://api.github.com/users/dimagi/events{/privacy}","received_events_url":"https://api.github.com/users/dimagi/received_events","type":"Organization","site_admin":false},"repo":{"id":20939877,"name":"commcare-odk","full_name":"dimagi/commcare-odk","owner":{"login":"dimagi","id":94514,"avatar_url":"https://avatars.githubusercontent.com/u/94514?v=3","gravatar_id":"","url":"https://api.github.com/users/dimagi","html_url":"https://github.com/dimagi","followers_url":"https://api.github.com/users/dimagi/followers","following_url":"https://api.github.com/users/dimagi/following{/other_user}","gists_url":"https://api.github.com/users/dimagi/gists{/gist_id}","starred_url":"https://api.github.com/users/dimagi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dimagi/subscriptions","organizations_url":"https://api.github.com/users/dimagi/orgs","repos_url":"https://api.github.com/users/dimagi/repos","events_url":"https://api.github.com/users/dimagi/events{/privacy}","received_events_url":"https://api.github.com/users/dimagi/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dimagi/commcare-odk","description":"","fork":false,"url":"https://api.github.com/repos/dimagi/commcare-odk","forks_url":"https://api.github.com/repos/dimagi/commcare-odk/forks","keys_url":"https://api.github.com/repos/dimagi/commcare-odk/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dimagi/commcare-odk/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dimagi/commcare-odk/teams","hooks_url":"https://api.github.com/repos/dimagi/commcare-odk/hooks","issue_events_url":"https://api.github.com/repos/dimagi/commcare-odk/issues/events{/number}","events_url":"https://api.github.com/repos/dimagi/commcare-odk/events","assignees_url":"https://api.github.com/repos/dimagi/commcare-odk/assignees{/user}","branches_url":"https://api.github.com/repos/dimagi/commcare-odk/branches{/branch}","tags_url":"https://api.github.com/repos/dimagi/commcare-odk/tags","blobs_url":"https://api.github.com/repos/dimagi/commcare-odk/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dimagi/commcare-odk/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dimagi/commcare-odk/git/refs{/sha}","trees_url":"https://api.github.com/repos/dimagi/commcare-odk/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dimagi/commcare-odk/statuses/{sha}","languages_url":"https://api.github.com/repos/dimagi/commcare-odk/languages","stargazers_url":"https://api.github.com/repos/dimagi/commcare-odk/stargazers","contributors_url":"https://api.github.com/repos/dimagi/commcare-odk/contributors","subscribers_url":"https://api.github.com/repos/dimagi/commcare-odk/subscribers","subscription_url":"https://api.github.com/repos/dimagi/commcare-odk/subscription","commits_url":"https://api.github.com/repos/dimagi/commcare-odk/commits{/sha}","git_commits_url":"https://api.github.com/repos/dimagi/commcare-odk/git/commits{/sha}","comments_url":"https://api.github.com/repos/dimagi/commcare-odk/comments{/number}","issue_comment_url":"https://api.github.com/repos/dimagi/commcare-odk/issues/comments/{number}","contents_url":"https://api.github.com/repos/dimagi/commcare-odk/contents/{+path}","compare_url":"https://api.github.com/repos/dimagi/commcare-odk/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dimagi/commcare-odk/merges","archive_url":"https://api.github.com/repos/dimagi/commcare-odk/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dimagi/commcare-odk/downloads","issues_url":"https://api.github.com/repos/dimagi/commcare-odk/issues{/number}","pulls_url":"https://api.github.com/repos/dimagi/commcare-odk/pulls{/number}","milestones_url":"https://api.github.com/repos/dimagi/commcare-odk/milestones{/number}","notifications_url":"https://api.github.com/repos/dimagi/commcare-odk/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dimagi/commcare-odk/labels{/name}","releases_url":"https://api.github.com/repos/dimagi/commcare-odk/releases{/id}","created_at":"2014-06-17T21:17:26Z","updated_at":"2014-12-16T17:49:41Z","pushed_at":"2014-12-16T17:49:43Z","git_url":"git://github.com/dimagi/commcare-odk.git","ssh_url":"git@github.com:dimagi/commcare-odk.git","clone_url":"https://github.com/dimagi/commcare-odk.git","svn_url":"https://github.com/dimagi/commcare-odk","homepage":null,"size":28636,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":6,"forks":4,"open_issues":6,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83"},"html":{"href":"https://github.com/dimagi/commcare-odk/pull/83"},"issue":{"href":"https://api.github.com/repos/dimagi/commcare-odk/issues/83"},"comments":{"href":"https://api.github.com/repos/dimagi/commcare-odk/issues/83/comments"},"review_comments":{"href":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83/comments"},"review_comment":{"href":"https://api.github.com/repos/dimagi/commcare-odk/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/dimagi/commcare-odk/pulls/83/commits"},"statuses":{"href":"https://api.github.com/repos/dimagi/commcare-odk/statuses/8c79bf60f82ab83db8cdc0f23db4a32e8b564dc7"}}}}