{"action":"created","comment":{"url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/comments/62954844","id":62954844,"diff_hunk":"@@ -0,0 +1,353 @@\n+/*\n+ * Licensed to the Apache Software Foundation (ASF) under one or more\n+ * contributor license agreements.  See the NOTICE file distributed with\n+ * this work for additional information regarding copyright ownership.\n+ * The ASF licenses this file to You under the Apache License, Version 2.0\n+ * (the \"License\"); you may not use this file except in compliance with\n+ * the License.  You may obtain a copy of the License at\n+ *\n+ *      http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+package org.apache.jackrabbit.oak.spi.security.authentication.external.impl.principal;\n+\n+import java.security.Principal;\n+import java.security.acl.Group;\n+import java.text.ParseException;\n+import java.util.Collections;\n+import java.util.Enumeration;\n+import java.util.HashSet;\n+import java.util.Iterator;\n+import java.util.Map;\n+import java.util.Set;\n+import javax.annotation.CheckForNull;\n+import javax.annotation.Nonnull;\n+import javax.annotation.Nullable;\n+import javax.jcr.PropertyType;\n+import javax.jcr.RepositoryException;\n+import javax.jcr.Value;\n+import javax.jcr.query.Query;\n+\n+import com.google.common.base.Predicates;\n+import com.google.common.collect.ImmutableSet;\n+import com.google.common.collect.Iterables;\n+import com.google.common.collect.Iterators;\n+import com.google.common.collect.Sets;\n+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;\n+import org.apache.jackrabbit.api.security.principal.PrincipalManager;\n+import org.apache.jackrabbit.api.security.user.Authorizable;\n+import org.apache.jackrabbit.api.security.user.UserManager;\n+import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;\n+import org.apache.jackrabbit.oak.api.PropertyState;\n+import org.apache.jackrabbit.oak.api.PropertyValue;\n+import org.apache.jackrabbit.oak.api.QueryEngine;\n+import org.apache.jackrabbit.oak.api.Result;\n+import org.apache.jackrabbit.oak.api.ResultRow;\n+import org.apache.jackrabbit.oak.api.Root;\n+import org.apache.jackrabbit.oak.api.Tree;\n+import org.apache.jackrabbit.oak.api.Type;\n+import org.apache.jackrabbit.oak.namepath.NamePathMapper;\n+import org.apache.jackrabbit.oak.spi.query.PropertyValues;\n+import org.apache.jackrabbit.oak.spi.security.authentication.external.basic.DefaultSyncConfig;\n+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl;\n+import org.apache.jackrabbit.oak.spi.security.principal.PrincipalProvider;\n+import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;\n+import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;\n+import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+/**\n+ * Implementation of the {@code PrincipalProvider} interface that exposes\n+ * 'external' principals of type {@link java.security.acl.Group}. 'External'\n+ * refers to the fact that these principals are defined and managed by an\n+ * {@link org.apache.jackrabbit.oak.spi.security.authentication.external.ExternalIdentityProvider}.\n+ *\n+ * For performance reasons this implementation doesn't lookup principals on the IDP\n+ * but relies on a persisted cache inside the repository where the names of these\n+ * external principals are synchronized to based on a configurable expiration time.\n+ *\n+ * Currently, the implementation respects the {@code rep:externalPrincipalNames}\n+ * properties, where group membership of external users gets synchronized if\n+ * {@link DefaultSyncConfig.User#getDynamicMembership() dynamic membership} has\n+ * been enabled. This synchronization is take care of by the {@link PrincipalSyncContext}.\n+ *\n+ * Please note that in contrast to the default principal provider implementation\n+ * shipped with Oak the group principals known and exposed by this provider are\n+ * not backed by a authorizable group and thus cannot be retrieved using\n+ * Jackrabbit user management API.\n+ */\n+class ExternalGroupPrincipalProvider implements PrincipalProvider, ExternalIdentityConstants {\n+\n+    private static final Logger log = LoggerFactory.getLogger(ExternalGroupPrincipalProvider.class);\n+\n+    private final Root root;\n+    private final NamePathMapper namePathMapper;\n+    private final UserManager userManager;\n+\n+    ExternalGroupPrincipalProvider(Root root, UserConfiguration uc, NamePathMapper namePathMapper) {\n+        this.root = root;\n+        this.namePathMapper = namePathMapper;\n+        userManager = uc.getUserManager(root, namePathMapper);\n+    }\n+\n+    //--------------------------------------------------< PrincipalProvider >---\n+    @Override\n+    public Principal getPrincipal(@Nonnull String principalName) {\n+        Result result = findPrincipals(principalName, true);\n+        if (result != null && result.getRows().iterator().hasNext()) {\n+            return new ExternalGroupPrincipal(principalName);\n+        } else {\n+            return null;\n+        }\n+    }\n+\n+    @Nonnull\n+    @Override\n+    public Set<Group> getGroupMembership(@Nonnull Principal principal) {\n+        if (!(principal instanceof Group)) {\n+            try {\n+                if (principal instanceof ItemBasedPrincipal) {\n+                    Tree t = root.getTree(((ItemBasedPrincipal) principal).getPath());\n+                    return getGroupPrincipals(t);\n+                } else {\n+                    return getGroupPrincipals(userManager.getAuthorizable(principal));\n+                }\n+            } catch (RepositoryException e) {\n+                log.debug(e.getMessage());\n+            }\n+\n+        }\n+        return ImmutableSet.of();\n+    }\n+\n+    @Nonnull\n+    @Override\n+    public Set<? extends Principal> getPrincipals(@Nonnull String userID) {\n+        try {\n+            return getGroupPrincipals(userManager.getAuthorizable(userID));\n+        } catch (RepositoryException e) {\n+            log.debug(e.getMessage());\n+            return ImmutableSet.of();\n+        }\n+    }\n+\n+    @Nonnull\n+    @Override\n+    public Iterator<? extends Principal> findPrincipals(@Nullable String nameHint, int searchType) {\n+        if (PrincipalManager.SEARCH_TYPE_NOT_GROUP != searchType) {\n+            Result result = findPrincipals(buildSearchPattern(nameHint), false);\n+            if (result != null) {\n+                return Iterators.filter(new GroupPrincipalIterator(nameHint, result), Predicates.notNull());\n+            }\n+        }\n+\n+        return Iterators.emptyIterator();\n+    }\n+\n+    @Nonnull\n+    @Override\n+    public Iterator<? extends Principal> findPrincipals(int searchType) {\n+        return findPrincipals(null, searchType);\n+    }\n+\n+    //--------------------------------------------------------------------------\n+    private Set<Group> getGroupPrincipals(@CheckForNull Authorizable authorizable) throws RepositoryException {\n+        if (authorizable != null && !authorizable.isGroup()) {\n+            Tree userTree = root.getTree(authorizable.getPath());\n+            return getGroupPrincipals(userTree);\n+        } else {\n+            return ImmutableSet.of();\n+        }\n+    }\n+\n+    private Set<Group> getGroupPrincipals(@Nonnull Tree userTree) {\n+        if (userTree.exists() && UserUtil.isType(userTree, AuthorizableType.USER) && userTree.hasProperty(REP_EXTERNAL_PRINCIPAL_NAMES)) {\n+            PropertyState ps = userTree.getProperty(REP_EXTERNAL_PRINCIPAL_NAMES);\n+            if (ps != null) {\n+                Set<Group> groupPrincipals = Sets.newHashSet();\n+                for (String principalName : ps.getValue(Type.STRINGS)) {\n+                    groupPrincipals.add(new ExternalGroupPrincipal(principalName));\n+                }\n+                return groupPrincipals;\n+            }\n+        }\n+        // no group principals defined\n+        return ImmutableSet.of();\n+    }\n+\n+    @CheckForNull\n+    private Result findPrincipals(@Nonnull String queryString, boolean exactMatch) {\n+        try {\n+            Map<String, ? extends PropertyValue> bindings = Collections.singletonMap(\"princNames\", PropertyValues.newString(queryString));\n+            String op = (exactMatch) ? \" = \" : \" LIKE \";\n+            String statement = \"SELECT '\" + REP_EXTERNAL_PRINCIPAL_NAMES + \"' FROM [rep:User] WHERE PROPERTY([\"","path":"oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/ExternalGroupPrincipalProvider.java","position":189,"original_position":189,"commit_id":"c60da86cba1c6957c4bc1d94db30f9839e135601","original_commit_id":"c60da86cba1c6957c4bc1d94db30f9839e135601","user":{"login":"tripodsan","id":917628,"avatar_url":"https://avatars.githubusercontent.com/u/917628?v=3","gravatar_id":"","url":"https://api.github.com/users/tripodsan","html_url":"https://github.com/tripodsan","followers_url":"https://api.github.com/users/tripodsan/followers","following_url":"https://api.github.com/users/tripodsan/following{/other_user}","gists_url":"https://api.github.com/users/tripodsan/gists{/gist_id}","starred_url":"https://api.github.com/users/tripodsan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tripodsan/subscriptions","organizations_url":"https://api.github.com/users/tripodsan/orgs","repos_url":"https://api.github.com/users/tripodsan/repos","events_url":"https://api.github.com/users/tripodsan/events{/privacy}","received_events_url":"https://api.github.com/users/tripodsan/received_events","type":"User","site_admin":false},"body":"consider use of: `String.format()`. this is barely readable :-)","created_at":"2016-05-12T01:45:48Z","updated_at":"2016-05-12T01:45:48Z","html_url":"https://github.com/apache/jackrabbit-oak/pull/52#discussion_r62954844","pull_request_url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52","_links":{"self":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/comments/62954844"},"html":{"href":"https://github.com/apache/jackrabbit-oak/pull/52#discussion_r62954844"},"pull_request":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52"}}},"pull_request":{"url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52","id":69767621,"html_url":"https://github.com/apache/jackrabbit-oak/pull/52","diff_url":"https://github.com/apache/jackrabbit-oak/pull/52.diff","patch_url":"https://github.com/apache/jackrabbit-oak/pull/52.patch","issue_url":"https://api.github.com/repos/apache/jackrabbit-oak/issues/52","number":52,"state":"open","locked":false,"title":"OAK-4101 Consider separate external (group) principal management","user":{"login":"tripodsan","id":917628,"avatar_url":"https://avatars.githubusercontent.com/u/917628?v=3","gravatar_id":"","url":"https://api.github.com/users/tripodsan","html_url":"https://github.com/tripodsan","followers_url":"https://api.github.com/users/tripodsan/followers","following_url":"https://api.github.com/users/tripodsan/following{/other_user}","gists_url":"https://api.github.com/users/tripodsan/gists{/gist_id}","starred_url":"https://api.github.com/users/tripodsan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tripodsan/subscriptions","organizations_url":"https://api.github.com/users/tripodsan/orgs","repos_url":"https://api.github.com/users/tripodsan/repos","events_url":"https://api.github.com/users/tripodsan/events{/privacy}","received_events_url":"https://api.github.com/users/tripodsan/received_events","type":"User","site_admin":false},"body":"I created this pull request in order to be able to discuss the proposed patch in https://issues.apache.org/jira/browse/OAK-4101. I'm not the original author.","created_at":"2016-05-12T01:32:26Z","updated_at":"2016-05-12T01:45:48Z","closed_at":null,"merged_at":null,"merge_commit_sha":"014319732a628abb8f577ef6d59db61b106c24b1","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52/commits","review_comments_url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52/comments","review_comment_url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/jackrabbit-oak/issues/52/comments","statuses_url":"https://api.github.com/repos/apache/jackrabbit-oak/statuses/c60da86cba1c6957c4bc1d94db30f9839e135601","head":{"label":"tripodsan:OAK-4101","ref":"OAK-4101","sha":"c60da86cba1c6957c4bc1d94db30f9839e135601","user":{"login":"tripodsan","id":917628,"avatar_url":"https://avatars.githubusercontent.com/u/917628?v=3","gravatar_id":"","url":"https://api.github.com/users/tripodsan","html_url":"https://github.com/tripodsan","followers_url":"https://api.github.com/users/tripodsan/followers","following_url":"https://api.github.com/users/tripodsan/following{/other_user}","gists_url":"https://api.github.com/users/tripodsan/gists{/gist_id}","starred_url":"https://api.github.com/users/tripodsan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tripodsan/subscriptions","organizations_url":"https://api.github.com/users/tripodsan/orgs","repos_url":"https://api.github.com/users/tripodsan/repos","events_url":"https://api.github.com/users/tripodsan/events{/privacy}","received_events_url":"https://api.github.com/users/tripodsan/received_events","type":"User","site_admin":false},"repo":{"id":14032356,"name":"jackrabbit-oak","full_name":"tripodsan/jackrabbit-oak","owner":{"login":"tripodsan","id":917628,"avatar_url":"https://avatars.githubusercontent.com/u/917628?v=3","gravatar_id":"","url":"https://api.github.com/users/tripodsan","html_url":"https://github.com/tripodsan","followers_url":"https://api.github.com/users/tripodsan/followers","following_url":"https://api.github.com/users/tripodsan/following{/other_user}","gists_url":"https://api.github.com/users/tripodsan/gists{/gist_id}","starred_url":"https://api.github.com/users/tripodsan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tripodsan/subscriptions","organizations_url":"https://api.github.com/users/tripodsan/orgs","repos_url":"https://api.github.com/users/tripodsan/repos","events_url":"https://api.github.com/users/tripodsan/events{/privacy}","received_events_url":"https://api.github.com/users/tripodsan/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tripodsan/jackrabbit-oak","description":"Mirror of Apache Jackrabbit Oak","fork":true,"url":"https://api.github.com/repos/tripodsan/jackrabbit-oak","forks_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/forks","keys_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/teams","hooks_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/hooks","issue_events_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/issues/events{/number}","events_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/events","assignees_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/assignees{/user}","branches_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/branches{/branch}","tags_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/tags","blobs_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/git/refs{/sha}","trees_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/statuses/{sha}","languages_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/languages","stargazers_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/stargazers","contributors_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/contributors","subscribers_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/subscribers","subscription_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/subscription","commits_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/commits{/sha}","git_commits_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/git/commits{/sha}","comments_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/comments{/number}","issue_comment_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/issues/comments{/number}","contents_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/contents/{+path}","compare_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/merges","archive_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/downloads","issues_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/issues{/number}","pulls_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/pulls{/number}","milestones_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/milestones{/number}","notifications_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/labels{/name}","releases_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/releases{/id}","deployments_url":"https://api.github.com/repos/tripodsan/jackrabbit-oak/deployments","created_at":"2013-11-01T00:56:12Z","updated_at":"2015-04-16T21:03:25Z","pushed_at":"2016-05-12T01:32:14Z","git_url":"git://github.com/tripodsan/jackrabbit-oak.git","ssh_url":"git@github.com:tripodsan/jackrabbit-oak.git","clone_url":"https://github.com/tripodsan/jackrabbit-oak.git","svn_url":"https://github.com/tripodsan/jackrabbit-oak","homepage":null,"size":57771,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"trunk"}},"base":{"label":"apache:trunk","ref":"trunk","sha":"1fdc52ec68513ca56abd662e9244ee3d1d7dc707","user":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":3657930,"name":"jackrabbit-oak","full_name":"apache/jackrabbit-oak","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/jackrabbit-oak","description":"Mirror of Apache Jackrabbit Oak","fork":false,"url":"https://api.github.com/repos/apache/jackrabbit-oak","forks_url":"https://api.github.com/repos/apache/jackrabbit-oak/forks","keys_url":"https://api.github.com/repos/apache/jackrabbit-oak/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/jackrabbit-oak/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/jackrabbit-oak/teams","hooks_url":"https://api.github.com/repos/apache/jackrabbit-oak/hooks","issue_events_url":"https://api.github.com/repos/apache/jackrabbit-oak/issues/events{/number}","events_url":"https://api.github.com/repos/apache/jackrabbit-oak/events","assignees_url":"https://api.github.com/repos/apache/jackrabbit-oak/assignees{/user}","branches_url":"https://api.github.com/repos/apache/jackrabbit-oak/branches{/branch}","tags_url":"https://api.github.com/repos/apache/jackrabbit-oak/tags","blobs_url":"https://api.github.com/repos/apache/jackrabbit-oak/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/jackrabbit-oak/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/jackrabbit-oak/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/jackrabbit-oak/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/jackrabbit-oak/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/jackrabbit-oak/languages","stargazers_url":"https://api.github.com/repos/apache/jackrabbit-oak/stargazers","contributors_url":"https://api.github.com/repos/apache/jackrabbit-oak/contributors","subscribers_url":"https://api.github.com/repos/apache/jackrabbit-oak/subscribers","subscription_url":"https://api.github.com/repos/apache/jackrabbit-oak/subscription","commits_url":"https://api.github.com/repos/apache/jackrabbit-oak/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/jackrabbit-oak/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/jackrabbit-oak/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/jackrabbit-oak/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/jackrabbit-oak/contents/{+path}","compare_url":"https://api.github.com/repos/apache/jackrabbit-oak/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/jackrabbit-oak/merges","archive_url":"https://api.github.com/repos/apache/jackrabbit-oak/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/jackrabbit-oak/downloads","issues_url":"https://api.github.com/repos/apache/jackrabbit-oak/issues{/number}","pulls_url":"https://api.github.com/repos/apache/jackrabbit-oak/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/jackrabbit-oak/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/jackrabbit-oak/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/jackrabbit-oak/labels{/name}","releases_url":"https://api.github.com/repos/apache/jackrabbit-oak/releases{/id}","deployments_url":"https://api.github.com/repos/apache/jackrabbit-oak/deployments","created_at":"2012-03-08T08:00:14Z","updated_at":"2016-05-10T01:07:12Z","pushed_at":"2016-05-12T01:32:27Z","git_url":"git://github.com/apache/jackrabbit-oak.git","ssh_url":"git@github.com:apache/jackrabbit-oak.git","clone_url":"https://github.com/apache/jackrabbit-oak.git","svn_url":"https://github.com/apache/jackrabbit-oak","homepage":null,"size":67284,"stargazers_count":145,"watchers_count":145,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":168,"mirror_url":"git://git.apache.org/jackrabbit-oak.git","open_issues_count":31,"forks":168,"open_issues":31,"watchers":145,"default_branch":"trunk"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52"},"html":{"href":"https://github.com/apache/jackrabbit-oak/pull/52"},"issue":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/issues/52"},"comments":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/issues/52/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/pulls/52/commits"},"statuses":{"href":"https://api.github.com/repos/apache/jackrabbit-oak/statuses/c60da86cba1c6957c4bc1d94db30f9839e135601"}}}}