{"action":"created","comment":{"url":"https://api.github.com/repos/Jasig/uPortal/pulls/comments/59653905","id":59653905,"diff_hunk":"@@ -0,0 +1,345 @@\n+/*\n+ * Copyright 2014 Jasig.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\");\n+ * you may not use this file except in compliance with the License.\n+ * 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+\n+package org.jasig.portal.groups.db;\n+\n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.HashMap;\n+import java.util.Iterator;\n+import java.util.LinkedList;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.concurrent.ExecutorService;\n+import java.util.concurrent.TimeUnit;\n+import org.apache.commons.logging.Log;\n+import org.apache.commons.logging.LogFactory;\n+import org.jasig.portal.EntityIdentifier;\n+import org.jasig.portal.groups.EntityImpl;\n+import org.jasig.portal.groups.EntityTestingGroupImpl;\n+import org.jasig.portal.groups.GroupsException;\n+import org.jasig.portal.groups.IEntity;\n+import org.jasig.portal.groups.IEntityGroup;\n+import org.jasig.portal.groups.IEntityGroupStore;\n+import org.jasig.portal.groups.IEntitySearcher;\n+import org.jasig.portal.groups.IEntityStore;\n+import org.jasig.portal.groups.IGroupConstants;\n+import org.jasig.portal.groups.IGroupMember;\n+import org.jasig.portal.groups.ILockableEntityGroup;\n+import org.jasig.portal.security.IPerson;\n+import org.springframework.beans.factory.annotation.Autowired;\n+import org.springframework.beans.factory.annotation.Value;\n+import org.springframework.context.ApplicationContext;\n+import org.springframework.context.support.AbstractApplicationContext;\n+import org.springframework.jdbc.core.JdbcTemplate;\n+import org.springframework.scheduling.annotation.Async;\n+import org.springframework.scheduling.annotation.Scheduled;\n+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;\n+\n+/**\n+ *\n+ * @author Chris White <christopher.white@manchester.ac.uk>\n+ */\n+public class ExternalDBGroupStore implements IEntityGroupStore, IEntityStore, IEntitySearcher {\n+\n+    public ExternalDBGroupStore(ApplicationContext context) {\n+        this.spring_context = context;\n+        spring_context.getAutowireCapableBeanFactory().autowireBean(this);\n+    }\n+\n+    @Autowired ThreadPoolTaskExecutor executor;\n+    @Autowired ThreadPoolTaskScheduler scheduler;\n+\n+    @Override\n+    public void finalize() throws Throwable {\n+        log.info(\"Closing Scheduled tasks -- \");\n+        ExecutorService pool = scheduler.getScheduledExecutor();\n+            log.info(\"Disbale new tasks from being submitted\");\n+           pool.shutdown(); // Disable new tasks from being submitted\n+            try {\n+                log.warn(\"waiting for existing tasks to teminate\");\n+              // Wait a while for existing tasks to terminate\n+              if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {\n+                  log.warn(\"Cancel currently executing tasks\");\n+                pool.shutdownNow(); // Cancel currently executing tasks\n+                // Wait a while for tasks to respond to being cancelled\n+                log.warn(\"Waiting for tasks to respond to being cancelled\");\n+                if (!pool.awaitTermination(5, TimeUnit.SECONDS))\n+                    log.error(\"Pool did not terminate\");\n+//                    System.err.println(\"Pool did not terminate\");\n+              }\n+            } catch (InterruptedException ie) {\n+              // (Re-)Cancel if current thread also interrupted\n+              pool.shutdownNow();\n+              // Preserve interrupt status\n+              Thread.currentThread().interrupt();\n+            }\n+            \n+//        scheduler.getScheduledExecutor().shutdownNow();\n+        scheduler.shutdown();\n+        executor.shutdown();\n+        try {\n+            if (spring_context instanceof AbstractApplicationContext)\n+                ((AbstractApplicationContext)this.spring_context).close();\n+        } finally {\n+            super.finalize();\n+        }\n+        \n+    }       \n+\n+    \n+    private final Log log = LogFactory.getLog(getClass());\n+    \n+//    public static final String ROOT_KEY = \"db_root\";\n+    public static final String ROOT_KEY = \"DB Root\";\n+    public static final String ROOT_DESC = \"A root group provided for the ExternalDBGroupStore.\";\n+    public static final IEntityGroup ROOT_GROUP = createRootGroup();\n+    \n+    @Value(value = \"${GroupStore.GroupQuery}\")\n+    private String query = \"\";\n+    @Value(value = \"${GroupStore.GroupMemberQuery}\")\n+    private String groupMemberQuery = \"\";\n+    @Value(value = \"${GroupStore.GroupMembershipQuery}\")\n+    private String groupMembershipQuery = \"\";\n+    @Value(value = \"${GroupStore.GroupEntityMembersQuery}\")\n+    private String groupEntityMembersQuery = \"\";\n+    private ApplicationContext spring_context = null;\n+    private final Map<String,IEntityGroup> groups = new HashMap<String, IEntityGroup>();\n+\n+    @Autowired\n+    private JdbcTemplate jdbcTemplate;\n+    \n+    \n+    @Scheduled(cron = \"${GroupStore.update.cron}\")\n+    private void updateSchedule() {\n+        this.updateGroups();\n+    }\n+    \n+    @Async\n+    protected void updateGroups() {\n+        List<Map<String, Object>> groupList = jdbcTemplate.queryForList(query);\n+            \n+            Map<String, IEntityGroup> newGroups  = new HashMap<String, IEntityGroup>();\n+            \n+            for (Map<String,Object> groupDef : groupList) {\n+                IEntityGroup group = new EntityTestingGroupImpl((String) groupDef.get(\"group_name\"), IPerson.class);\n+                group.setName((String) groupDef.get(\"group_name\"));\n+                group.setDescription((String) groupDef.get(\"group_name\"));\n+                newGroups.put(group.getName(), group);\n+            }\n+            \n+            synchronized (groups) {\n+                groups.clear();\n+                groups.putAll(newGroups);\n+            }\n+            log.warn(groups);\n+    }\n+    \n+    @Override\n+    public boolean contains(IEntityGroup group, IGroupMember member) throws GroupsException {","path":"uportal-war/src/main/java/org/jasig/portal/groups/db/ExternalDBGroupStore.java","position":153,"original_position":153,"commit_id":"dffbb2941a8179a2f78fa99742440e2cb7b6e547","original_commit_id":"dffbb2941a8179a2f78fa99742440e2cb7b6e547","user":{"login":"drewwills","id":1127174,"avatar_url":"https://avatars.githubusercontent.com/u/1127174?v=3","gravatar_id":"","url":"https://api.github.com/users/drewwills","html_url":"https://github.com/drewwills","followers_url":"https://api.github.com/users/drewwills/followers","following_url":"https://api.github.com/users/drewwills/following{/other_user}","gists_url":"https://api.github.com/users/drewwills/gists{/gist_id}","starred_url":"https://api.github.com/users/drewwills/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drewwills/subscriptions","organizations_url":"https://api.github.com/users/drewwills/orgs","repos_url":"https://api.github.com/users/drewwills/repos","events_url":"https://api.github.com/users/drewwills/events{/privacy}","received_events_url":"https://api.github.com/users/drewwills/received_events","type":"User","site_admin":false},"body":"Two questions about this method:\r\n\r\n- Can this flavor of groups contain child groups?  (Can those groups be other flavors?)  If not, you may want to check for those qualities and `return false` quickly\r\n- Doesn't this method get called a lot?  Is there a way to implement this method that doesn't involve a database query for every invocation?","created_at":"2016-04-14T01:42:57Z","updated_at":"2016-04-14T01:42:57Z","html_url":"https://github.com/Jasig/uPortal/pull/401#discussion_r59653905","pull_request_url":"https://api.github.com/repos/Jasig/uPortal/pulls/401","_links":{"self":{"href":"https://api.github.com/repos/Jasig/uPortal/pulls/comments/59653905"},"html":{"href":"https://github.com/Jasig/uPortal/pull/401#discussion_r59653905"},"pull_request":{"href":"https://api.github.com/repos/Jasig/uPortal/pulls/401"}}},"pull_request":{"url":"https://api.github.com/repos/Jasig/uPortal/pulls/401","id":19593859,"html_url":"https://github.com/Jasig/uPortal/pull/401","diff_url":"https://github.com/Jasig/uPortal/pull/401.diff","patch_url":"https://github.com/Jasig/uPortal/pull/401.patch","issue_url":"https://api.github.com/repos/Jasig/uPortal/issues/401","number":401,"state":"open","locked":false,"title":"External Database Group store implementation","user":{"login":"cjgwhite","id":1655394,"avatar_url":"https://avatars.githubusercontent.com/u/1655394?v=3","gravatar_id":"","url":"https://api.github.com/users/cjgwhite","html_url":"https://github.com/cjgwhite","followers_url":"https://api.github.com/users/cjgwhite/followers","following_url":"https://api.github.com/users/cjgwhite/following{/other_user}","gists_url":"https://api.github.com/users/cjgwhite/gists{/gist_id}","starred_url":"https://api.github.com/users/cjgwhite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cjgwhite/subscriptions","organizations_url":"https://api.github.com/users/cjgwhite/orgs","repos_url":"https://api.github.com/users/cjgwhite/repos","events_url":"https://api.github.com/users/cjgwhite/events{/privacy}","received_events_url":"https://api.github.com/users/cjgwhite/received_events","type":"User","site_admin":false},"body":"Implementation of the GroupStore API for using an external Database to hold username<-->group_name information.\r\n\r\nBack in November 2013 (I believe) Anthony Colebourne started a discussion concerning building a custom Group source for uPortal that would work against an external Database source…\r\n\r\nhttp://jasig.275507.n4.nabble.com/Custom-group-source-tt4661293.html\r\n\r\nhttp://jasig.275507.n4.nabble.com/Custom-Group-Source-td4663482.html\r\n\r\nWe still have a requirement to be able to drive groups and group membership from an external database. To that end we have recently got round to doing the work to create an implementation of the GroupService to meet this requirement. \r\n\r\nthis is the pull request of our implementation. It is heavily based on the smartLDAP implementation. Any comments / suggestions would be very much appreciated.\r\n\r\nThis implementation assumes no hierarchy in the groups held within the external database -- all groups are direct children of the Root Group for the GroupStore ('DB Root'). The implication being that DB backing this is the simplest possible structure (username <--> group_name pairs held in a single table). There is no reason why this could not be backed by more complex structures if required with changes to the supplied queries (implementors should change these for their own implementation anyway).\r\n\r\nBest Regards\r\nChris","created_at":"2014-08-11T10:50:54Z","updated_at":"2016-04-14T01:42:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"892b9d078aabadfb459595dea2d74bd78bf709df","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Jasig/uPortal/pulls/401/commits","review_comments_url":"https://api.github.com/repos/Jasig/uPortal/pulls/401/comments","review_comment_url":"https://api.github.com/repos/Jasig/uPortal/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Jasig/uPortal/issues/401/comments","statuses_url":"https://api.github.com/repos/Jasig/uPortal/statuses/dffbb2941a8179a2f78fa99742440e2cb7b6e547","head":{"label":"University-of-Manchester:jdbc-groups","ref":"jdbc-groups","sha":"dffbb2941a8179a2f78fa99742440e2cb7b6e547","user":{"login":"University-of-Manchester","id":6094326,"avatar_url":"https://avatars.githubusercontent.com/u/6094326?v=3","gravatar_id":"","url":"https://api.github.com/users/University-of-Manchester","html_url":"https://github.com/University-of-Manchester","followers_url":"https://api.github.com/users/University-of-Manchester/followers","following_url":"https://api.github.com/users/University-of-Manchester/following{/other_user}","gists_url":"https://api.github.com/users/University-of-Manchester/gists{/gist_id}","starred_url":"https://api.github.com/users/University-of-Manchester/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/University-of-Manchester/subscriptions","organizations_url":"https://api.github.com/users/University-of-Manchester/orgs","repos_url":"https://api.github.com/users/University-of-Manchester/repos","events_url":"https://api.github.com/users/University-of-Manchester/events{/privacy}","received_events_url":"https://api.github.com/users/University-of-Manchester/received_events","type":"Organization","site_admin":false},"repo":{"id":17922711,"name":"uPortal","full_name":"University-of-Manchester/uPortal","owner":{"login":"University-of-Manchester","id":6094326,"avatar_url":"https://avatars.githubusercontent.com/u/6094326?v=3","gravatar_id":"","url":"https://api.github.com/users/University-of-Manchester","html_url":"https://github.com/University-of-Manchester","followers_url":"https://api.github.com/users/University-of-Manchester/followers","following_url":"https://api.github.com/users/University-of-Manchester/following{/other_user}","gists_url":"https://api.github.com/users/University-of-Manchester/gists{/gist_id}","starred_url":"https://api.github.com/users/University-of-Manchester/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/University-of-Manchester/subscriptions","organizations_url":"https://api.github.com/users/University-of-Manchester/orgs","repos_url":"https://api.github.com/users/University-of-Manchester/repos","events_url":"https://api.github.com/users/University-of-Manchester/events{/privacy}","received_events_url":"https://api.github.com/users/University-of-Manchester/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/University-of-Manchester/uPortal","description":"Enterprise open source portal built by and for the higher education community.","fork":true,"url":"https://api.github.com/repos/University-of-Manchester/uPortal","forks_url":"https://api.github.com/repos/University-of-Manchester/uPortal/forks","keys_url":"https://api.github.com/repos/University-of-Manchester/uPortal/keys{/key_id}","collaborators_url":"https://api.github.com/repos/University-of-Manchester/uPortal/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/University-of-Manchester/uPortal/teams","hooks_url":"https://api.github.com/repos/University-of-Manchester/uPortal/hooks","issue_events_url":"https://api.github.com/repos/University-of-Manchester/uPortal/issues/events{/number}","events_url":"https://api.github.com/repos/University-of-Manchester/uPortal/events","assignees_url":"https://api.github.com/repos/University-of-Manchester/uPortal/assignees{/user}","branches_url":"https://api.github.com/repos/University-of-Manchester/uPortal/branches{/branch}","tags_url":"https://api.github.com/repos/University-of-Manchester/uPortal/tags","blobs_url":"https://api.github.com/repos/University-of-Manchester/uPortal/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/University-of-Manchester/uPortal/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/University-of-Manchester/uPortal/git/refs{/sha}","trees_url":"https://api.github.com/repos/University-of-Manchester/uPortal/git/trees{/sha}","statuses_url":"https://api.github.com/repos/University-of-Manchester/uPortal/statuses/{sha}","languages_url":"https://api.github.com/repos/University-of-Manchester/uPortal/languages","stargazers_url":"https://api.github.com/repos/University-of-Manchester/uPortal/stargazers","contributors_url":"https://api.github.com/repos/University-of-Manchester/uPortal/contributors","subscribers_url":"https://api.github.com/repos/University-of-Manchester/uPortal/subscribers","subscription_url":"https://api.github.com/repos/University-of-Manchester/uPortal/subscription","commits_url":"https://api.github.com/repos/University-of-Manchester/uPortal/commits{/sha}","git_commits_url":"https://api.github.com/repos/University-of-Manchester/uPortal/git/commits{/sha}","comments_url":"https://api.github.com/repos/University-of-Manchester/uPortal/comments{/number}","issue_comment_url":"https://api.github.com/repos/University-of-Manchester/uPortal/issues/comments{/number}","contents_url":"https://api.github.com/repos/University-of-Manchester/uPortal/contents/{+path}","compare_url":"https://api.github.com/repos/University-of-Manchester/uPortal/compare/{base}...{head}","merges_url":"https://api.github.com/repos/University-of-Manchester/uPortal/merges","archive_url":"https://api.github.com/repos/University-of-Manchester/uPortal/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/University-of-Manchester/uPortal/downloads","issues_url":"https://api.github.com/repos/University-of-Manchester/uPortal/issues{/number}","pulls_url":"https://api.github.com/repos/University-of-Manchester/uPortal/pulls{/number}","milestones_url":"https://api.github.com/repos/University-of-Manchester/uPortal/milestones{/number}","notifications_url":"https://api.github.com/repos/University-of-Manchester/uPortal/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/University-of-Manchester/uPortal/labels{/name}","releases_url":"https://api.github.com/repos/University-of-Manchester/uPortal/releases{/id}","deployments_url":"https://api.github.com/repos/University-of-Manchester/uPortal/deployments","created_at":"2014-03-19T22:26:12Z","updated_at":"2014-06-19T11:38:33Z","pushed_at":"2014-07-23T10:12:55Z","git_url":"git://github.com/University-of-Manchester/uPortal.git","ssh_url":"git@github.com:University-of-Manchester/uPortal.git","clone_url":"https://github.com/University-of-Manchester/uPortal.git","svn_url":"https://github.com/University-of-Manchester/uPortal","homepage":"http://www.jasig.org/uportal","size":141714,"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":"master"}},"base":{"label":"Jasig:master","ref":"master","sha":"b7187910eb47c695138c6e2c1294c9b375ccf1ce","user":{"login":"Jasig","id":884285,"avatar_url":"https://avatars.githubusercontent.com/u/884285?v=3","gravatar_id":"","url":"https://api.github.com/users/Jasig","html_url":"https://github.com/Jasig","followers_url":"https://api.github.com/users/Jasig/followers","following_url":"https://api.github.com/users/Jasig/following{/other_user}","gists_url":"https://api.github.com/users/Jasig/gists{/gist_id}","starred_url":"https://api.github.com/users/Jasig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Jasig/subscriptions","organizations_url":"https://api.github.com/users/Jasig/orgs","repos_url":"https://api.github.com/users/Jasig/repos","events_url":"https://api.github.com/users/Jasig/events{/privacy}","received_events_url":"https://api.github.com/users/Jasig/received_events","type":"Organization","site_admin":false},"repo":{"id":2614085,"name":"uPortal","full_name":"Jasig/uPortal","owner":{"login":"Jasig","id":884285,"avatar_url":"https://avatars.githubusercontent.com/u/884285?v=3","gravatar_id":"","url":"https://api.github.com/users/Jasig","html_url":"https://github.com/Jasig","followers_url":"https://api.github.com/users/Jasig/followers","following_url":"https://api.github.com/users/Jasig/following{/other_user}","gists_url":"https://api.github.com/users/Jasig/gists{/gist_id}","starred_url":"https://api.github.com/users/Jasig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Jasig/subscriptions","organizations_url":"https://api.github.com/users/Jasig/orgs","repos_url":"https://api.github.com/users/Jasig/repos","events_url":"https://api.github.com/users/Jasig/events{/privacy}","received_events_url":"https://api.github.com/users/Jasig/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Jasig/uPortal","description":"Enterprise open source portal built by and for the higher education community.","fork":false,"url":"https://api.github.com/repos/Jasig/uPortal","forks_url":"https://api.github.com/repos/Jasig/uPortal/forks","keys_url":"https://api.github.com/repos/Jasig/uPortal/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Jasig/uPortal/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Jasig/uPortal/teams","hooks_url":"https://api.github.com/repos/Jasig/uPortal/hooks","issue_events_url":"https://api.github.com/repos/Jasig/uPortal/issues/events{/number}","events_url":"https://api.github.com/repos/Jasig/uPortal/events","assignees_url":"https://api.github.com/repos/Jasig/uPortal/assignees{/user}","branches_url":"https://api.github.com/repos/Jasig/uPortal/branches{/branch}","tags_url":"https://api.github.com/repos/Jasig/uPortal/tags","blobs_url":"https://api.github.com/repos/Jasig/uPortal/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Jasig/uPortal/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Jasig/uPortal/git/refs{/sha}","trees_url":"https://api.github.com/repos/Jasig/uPortal/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Jasig/uPortal/statuses/{sha}","languages_url":"https://api.github.com/repos/Jasig/uPortal/languages","stargazers_url":"https://api.github.com/repos/Jasig/uPortal/stargazers","contributors_url":"https://api.github.com/repos/Jasig/uPortal/contributors","subscribers_url":"https://api.github.com/repos/Jasig/uPortal/subscribers","subscription_url":"https://api.github.com/repos/Jasig/uPortal/subscription","commits_url":"https://api.github.com/repos/Jasig/uPortal/commits{/sha}","git_commits_url":"https://api.github.com/repos/Jasig/uPortal/git/commits{/sha}","comments_url":"https://api.github.com/repos/Jasig/uPortal/comments{/number}","issue_comment_url":"https://api.github.com/repos/Jasig/uPortal/issues/comments{/number}","contents_url":"https://api.github.com/repos/Jasig/uPortal/contents/{+path}","compare_url":"https://api.github.com/repos/Jasig/uPortal/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Jasig/uPortal/merges","archive_url":"https://api.github.com/repos/Jasig/uPortal/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Jasig/uPortal/downloads","issues_url":"https://api.github.com/repos/Jasig/uPortal/issues{/number}","pulls_url":"https://api.github.com/repos/Jasig/uPortal/pulls{/number}","milestones_url":"https://api.github.com/repos/Jasig/uPortal/milestones{/number}","notifications_url":"https://api.github.com/repos/Jasig/uPortal/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Jasig/uPortal/labels{/name}","releases_url":"https://api.github.com/repos/Jasig/uPortal/releases{/id}","deployments_url":"https://api.github.com/repos/Jasig/uPortal/deployments","created_at":"2011-10-20T15:34:22Z","updated_at":"2016-03-25T16:05:40Z","pushed_at":"2016-04-12T22:07:46Z","git_url":"git://github.com/Jasig/uPortal.git","ssh_url":"git@github.com:Jasig/uPortal.git","clone_url":"https://github.com/Jasig/uPortal.git","svn_url":"https://github.com/Jasig/uPortal","homepage":"http://www.jasig.org/uportal","size":151698,"stargazers_count":116,"watchers_count":116,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":177,"mirror_url":null,"open_issues_count":22,"forks":177,"open_issues":22,"watchers":116,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Jasig/uPortal/pulls/401"},"html":{"href":"https://github.com/Jasig/uPortal/pull/401"},"issue":{"href":"https://api.github.com/repos/Jasig/uPortal/issues/401"},"comments":{"href":"https://api.github.com/repos/Jasig/uPortal/issues/401/comments"},"review_comments":{"href":"https://api.github.com/repos/Jasig/uPortal/pulls/401/comments"},"review_comment":{"href":"https://api.github.com/repos/Jasig/uPortal/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Jasig/uPortal/pulls/401/commits"},"statuses":{"href":"https://api.github.com/repos/Jasig/uPortal/statuses/dffbb2941a8179a2f78fa99742440e2cb7b6e547"}}}}