{"action":"created","comment":{"url":"https://api.github.com/repos/codice/ddf/pulls/comments/227151222","pull_request_review_id":167125426,"id":227151222,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyNzE1MTIyMg==","diff_hunk":"@@ -13,110 +13,434 @@\n  */\n package ddf.catalog.util.impl;\n \n-import ddf.catalog.CatalogFramework;\n+import static org.apache.commons.lang.Validate.notNull;\n+\n+import com.google.common.annotations.VisibleForTesting;\n+import com.google.common.base.Objects;\n+import com.google.common.cache.Cache;\n+import com.google.common.cache.CacheBuilder;\n+import com.google.common.cache.RemovalNotification;\n+import com.google.common.util.concurrent.Futures;\n+import com.google.common.util.concurrent.ListeningExecutorService;\n+import com.google.common.util.concurrent.MoreExecutors;\n+import ddf.catalog.source.CatalogProvider;\n+import ddf.catalog.source.CatalogStore;\n+import ddf.catalog.source.ConnectedSource;\n+import ddf.catalog.source.FederatedSource;\n import ddf.catalog.source.Source;\n+import ddf.catalog.source.SourceMonitor;\n+import java.util.Collection;\n+import java.util.Collections;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.List;\n import java.util.Map;\n+import java.util.Map.Entry;\n import java.util.Optional;\n-import java.util.concurrent.Executors;\n+import java.util.Set;\n+import java.util.concurrent.CancellationException;\n+import java.util.concurrent.ExecutionException;\n+import java.util.concurrent.ExecutorService;\n+import java.util.concurrent.Future;\n import java.util.concurrent.ScheduledExecutorService;\n-import java.util.concurrent.ScheduledFuture;\n import java.util.concurrent.TimeUnit;\n-import org.codice.ddf.platform.util.StandardThreadFactoryBuilder;\n+import java.util.concurrent.TimeoutException;\n+import java.util.stream.Collectors;\n+import java.util.stream.Stream;\n+import org.apache.commons.collections4.CollectionUtils;\n+import org.apache.commons.lang.StringUtils;\n+import org.osgi.framework.Bundle;\n+import org.osgi.framework.BundleContext;\n+import org.osgi.framework.FrameworkUtil;\n import org.slf4j.Logger;\n import org.slf4j.LoggerFactory;\n \n /**\n- * The SourcePoller is the scheduler of the task to poll all configured sources at a fixed interval\n- * to determine their availability. It is created by the CatalogFramework's blueprint.\n+ * The {@link SourcePoller} checks the availability of all configured {@link Source}s and provides a\n+ * non-blocking solution to check whether a {@link Source} is available. The {@link SourcePoller}\n+ * can be scheduled to execute at a configured rate, i.e., the polling interval.\n+ *\n+ * <p>This class maintains a list of all of the {@link Source}s and their last known availability.\n+ * {@link Source}s are added to, or removed from, this list when they are created or destroyed. A\n+ * cached map is maintained of all the {@link Source}s and their last known availability, i.e.,\n+ * {@link SourceStatus}.\n  *\n- * <p>An isAvailable() method is included in this class so that the caller, nominally the\n- * CatalogFramework, can retrieve the cached availability of a specific source, or have it polled on\n- * demand if there is no availability status cached.\n+ * @see Source#isAvailable()\n+ * @see Source#isAvailable(SourceMonitor)\n+ * @see ddf.catalog.CatalogFramework#getSourceInfo(ddf.catalog.operation.SourceInfoRequest)\n  */\n public class SourcePoller {\n+\n   private static final Logger LOGGER = LoggerFactory.getLogger(SourcePoller.class);\n \n-  private static final int INITIAL_DELAY = 0;\n+  private static final int DEFAULT_POLL_INTERVAL = 1;\n+\n+  private static final TimeUnit DEFAULT_POLL_INTERVAL_TIME_UNIT = TimeUnit.MINUTES;\n+\n+  private static final int DEFAULT_TIMEOUT = 30;\n+\n+  private static final TimeUnit DEFAULT_TIMEOUT_TIME_UNIT = TimeUnit.SECONDS;\n+\n+  private volatile long pollInterval = DEFAULT_POLL_INTERVAL;\n+\n+  private volatile TimeUnit pollIntervalTimeUnit = DEFAULT_POLL_INTERVAL_TIME_UNIT;\n+\n+  private List<ConnectedSource> connectedSources = Collections.emptyList();\n \n-  private int interval = 1;\n+  private List<FederatedSource> federatedSources = Collections.emptyList();\n \n-  private ScheduledExecutorService scheduler;\n+  private List<CatalogProvider> catalogProviders = Collections.emptyList();\n \n-  private ScheduledFuture<?> handle;\n+  private List<CatalogStore> catalogStores = Collections.emptyList();\n \n-  private SourcePollerRunner runner;\n+  private final ListeningExecutorService sourceAvailabilityChecksThreadPool;\n+\n+  private ScheduledExecutorService pollAllSourcesThreadPool;\n+\n+  private final Cache<SourceKey, SourceAvailability> cache;\n+\n+  public SourcePoller(\n+      ExecutorService sourceAvailabilityChecksThreadPool,\n+      ScheduledExecutorService pollAllSourcesThreadPool) {\n+    this(\n+        sourceAvailabilityChecksThreadPool,\n+        pollAllSourcesThreadPool,\n+        DEFAULT_POLL_INTERVAL,\n+        DEFAULT_POLL_INTERVAL_TIME_UNIT);\n+  }\n+\n+  /** Starts a process to periodically call {@link #pollAllSources()} */\n+  @VisibleForTesting\n+  SourcePoller(\n+      ExecutorService sourceAvailabilityChecksThreadPool,\n+      ScheduledExecutorService pollAllSourcesThreadPool,\n+      final long pollInterval,\n+      final TimeUnit pollIntervalTimeUnit) {\n+    this.sourceAvailabilityChecksThreadPool =\n+        MoreExecutors.listeningDecorator(sourceAvailabilityChecksThreadPool);\n+    this.pollAllSourcesThreadPool = pollAllSourcesThreadPool;\n+\n+    LOGGER.debug(\n+        \"Scheduling ExecutorService at fixed rate of {} {} with an initial delay of {}\",\n+        pollInterval,\n+        pollIntervalTimeUnit,\n+        pollInterval);\n+    pollAllSourcesThreadPool.scheduleAtFixedRate(\n+        this::pollAllSources, pollInterval, pollInterval, pollIntervalTimeUnit);\n+\n+    cache =\n+        CacheBuilder.newBuilder()\n+            .maximumSize(1000)\n+            .removalListener(\n+                (RemovalNotification<SourceKey, SourceAvailability> removalNotification) -> {\n+                  LOGGER.debug(\n+                      \"Removing SourceKey {} from cache. cause={}. Cancelling any futures that are checking availability for that entry\",\n+                      removalNotification.getKey(),\n+                      removalNotification.getCause());\n+                })\n+            .build();\n+\n+    setPollInterval(pollInterval, pollIntervalTimeUnit);\n+  }\n \n   /**\n-   * Constructor to schedule the SourcePollerRunner to execute immediately and at a fixed interval,\n-   * currently set at every 1 minute. This constructor is invoked by the CatalogFramework's\n-   * blueprint.\n+   * This method is used to keep the cache up to date.\n    *\n-   * @param incomingRunner the SourcePollerRunner to use for polling\n+   * <p>The current {@link Source}s configured by {@link #setConnectedSources(List)}, {@link\n+   * #setFederatedSources(List)}, {@link #setCatalogProviders(List)}, and {@link\n+   * #setCatalogStores(List)} are discovered.\n+   *\n+   * <p>A process to check (or recheck) and cache the availability of each {@link Source} is\n+   * performed.\n    */\n-  public SourcePoller(SourcePollerRunner incomingRunner) {\n+  public synchronized void pollAllSources() {\n+    try {\n+      final Set<Source> sources =\n+          Stream.of(connectedSources, federatedSources, catalogProviders, catalogStores)\n+              .flatMap(Collection::stream)\n+              .collect(Collectors.toSet());\n+\n+      LOGGER.trace(\"Found {} sources\", sources.size());\n+\n+      // Unbind orphaned entries in the map caused by when sources are modified and their key is\n+      // different\n+      final Map<SourceKey, Source> sourceKeyMap =\n+          sources\n+              .stream()\n+              .collect(\n+                  Collectors.toMap(\n+                      SourceKey::new,\n+                      source -> source,\n+                      (source, anotherSourceWithTheSameSourceKey) -> {\n+                        LOGGER.warn(\n+                            \"Found different sources with the same SourceKey with id={}. This means that the SourcePoller might not report accurate availability for these sources in the CatalogFramework. Confirm that every source has a unique id, and try restarting.\",\n+                            source.getId());\n+                        return source;\n+                      }));\n+\n+      final Set<SourceKey> currentSourceKeys = sourceKeyMap.keySet();\n+      final Set<SourceKey> sourceKeysInTheCache = cache.asMap().keySet();\n+\n+      final Set<SourceKey> sourceKeysToRemove = new HashSet<>(sourceKeysInTheCache);\n+      sourceKeysToRemove.removeAll(currentSourceKeys);\n+      if (CollectionUtils.isNotEmpty(sourceKeysToRemove)) {\n+        LOGGER.debug(\n+            \"Found {} SourceKeys to remove from the cache. This is caused by when sources are destroyed or modified in a way that their SourceKey changes. Removing these from the cache now\",\n+            sourceKeysToRemove.size());\n+        sourceKeysToRemove.forEach(cache::invalidate);\n+      }\n+\n+      Map<SourceKey, Future<SourceAvailability>> availabilityFutures =\n+          startAvailabilityChecks(sourceKeyMap);\n+      CacheResults(availabilityFutures);\n+    } catch (Throwable throwable) {\n+      LOGGER.warn(\"Scheduled polling of sources failed\", throwable);\n+    }\n+  }\n+\n+  private Map<SourceKey, Future<SourceAvailability>> startAvailabilityChecks(\n+      final Map<SourceKey, Source> sourceKeyMap) {\n+    Map<SourceKey, Future<SourceAvailability>> availabilityFutures = new HashMap<>();\n+    for (final Entry<SourceKey, Source> entry : sourceKeyMap.entrySet()) {\n+      final Source source = entry.getValue();\n+      final SourceKey sourceKey = entry.getKey();\n \n-    this.runner = incomingRunner;\n+      boolean sourceIsNotInCache = cache.getIfPresent(sourceKey) == null;\n+      if (sourceIsNotInCache) {\n+        LOGGER.debug(\"Found a new SourceKey for source id={}\", source.getId());\n+        cache.put(sourceKey, SourceAvailability.unknown());\n+      }\n \n-    scheduler =\n-        Executors.newSingleThreadScheduledExecutor(\n-            StandardThreadFactoryBuilder.newThreadFactory(\"sourcePollerThread\"));\n+      LOGGER.trace(\"Starting a process to check the availability of source id={}\", source.getId());\n+      Future<SourceAvailability> sourceAvailabilityFuture =\n+          Futures.withTimeout(\n+              sourceAvailabilityChecksThreadPool.submit(() -> checkAvailability(source)),\n+              DEFAULT_TIMEOUT,\n+              DEFAULT_TIMEOUT_TIME_UNIT,\n+              pollAllSourcesThreadPool);\n+      availabilityFutures.put(sourceKey, sourceAvailabilityFuture);\n+    }\n+    LOGGER.trace(\"Successfully submitted availability checks for all of the sources\");\n+    return availabilityFutures;\n+  }\n+\n+  private void CacheResults(Map<SourceKey, Future<SourceAvailability>> availabilityFutures) {\n+    for (final Entry<SourceKey, Future<SourceAvailability>> entry :\n+        availabilityFutures.entrySet()) {\n+      final Future<SourceAvailability> future = entry.getValue();\n+      final SourceKey sourceKey = entry.getKey();\n+      final String sourceId = sourceKey.id;\n+\n+      try {\n+        SourceAvailability result = future.get();","path":"catalog/core/catalog-core-api-impl/src/main/java/ddf/catalog/util/impl/SourcePoller.java","position":null,"original_position":246,"commit_id":"e391f7e0a1a2dc6c0a62d86d846090fdc8ff425a","original_commit_id":"deba986462ef7b69c230419676c94ccb5fb4964f","user":{"login":"kcover","id":22778496,"node_id":"MDQ6VXNlcjIyNzc4NDk2","avatar_url":"https://avatars0.githubusercontent.com/u/22778496?v=4","gravatar_id":"","url":"https://api.github.com/users/kcover","html_url":"https://github.com/kcover","followers_url":"https://api.github.com/users/kcover/followers","following_url":"https://api.github.com/users/kcover/following{/other_user}","gists_url":"https://api.github.com/users/kcover/gists{/gist_id}","starred_url":"https://api.github.com/users/kcover/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kcover/subscriptions","organizations_url":"https://api.github.com/users/kcover/orgs","repos_url":"https://api.github.com/users/kcover/repos","events_url":"https://api.github.com/users/kcover/events{/privacy}","received_events_url":"https://api.github.com/users/kcover/received_events","type":"User","site_admin":false},"body":"Apparently Futures.inCompletionOrder() is in Beta so I'm not sure if we want to use that. (See the annotation at the top left) https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/util/concurrent/Futures.html#inCompletionOrder-java.lang.Iterable-\r\n\r\nUsing Futures.addCallback() seems pretty similar to just having the availability check thread set it's own status and we got rid of that to make things simpler. Is that still something we want to do?","created_at":"2018-10-22T21:50:57Z","updated_at":"2018-10-22T21:50:57Z","html_url":"https://github.com/codice/ddf/pull/3876#discussion_r227151222","pull_request_url":"https://api.github.com/repos/codice/ddf/pulls/3876","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/codice/ddf/pulls/comments/227151222"},"html":{"href":"https://github.com/codice/ddf/pull/3876#discussion_r227151222"},"pull_request":{"href":"https://api.github.com/repos/codice/ddf/pulls/3876"}},"in_reply_to_id":226532756},"pull_request":{"url":"https://api.github.com/repos/codice/ddf/pulls/3876","id":222313180,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIyMzEzMTgw","html_url":"https://github.com/codice/ddf/pull/3876","diff_url":"https://github.com/codice/ddf/pull/3876.diff","patch_url":"https://github.com/codice/ddf/pull/3876.patch","issue_url":"https://api.github.com/repos/codice/ddf/issues/3876","number":3876,"state":"open","locked":false,"title":"[WIP] [2.13.x] DDF-1366 Re-implemented the SourcePoller","user":{"login":"kcover","id":22778496,"node_id":"MDQ6VXNlcjIyNzc4NDk2","avatar_url":"https://avatars0.githubusercontent.com/u/22778496?v=4","gravatar_id":"","url":"https://api.github.com/users/kcover","html_url":"https://github.com/kcover","followers_url":"https://api.github.com/users/kcover/followers","following_url":"https://api.github.com/users/kcover/following{/other_user}","gists_url":"https://api.github.com/users/kcover/gists{/gist_id}","starred_url":"https://api.github.com/users/kcover/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kcover/subscriptions","organizations_url":"https://api.github.com/users/kcover/orgs","repos_url":"https://api.github.com/users/kcover/repos","events_url":"https://api.github.com/users/kcover/events{/privacy}","received_events_url":"https://api.github.com/users/kcover/received_events","type":"User","site_admin":false},"body":"##### This is a WIP PR. Do not merge.\r\n\r\nThis PR was created from #3858.\r\n\r\n#### Who is reviewing it? \r\n\r\n@peterhuffer @pklinef @lessarderic @clockard @emmberk @coyotesqrl @stustison \r\n\r\n#### Select relevant component teams: \r\n\r\n#### Ask 2 committers to review/merge the PR and tag them here.\r\n\r\n#### How should this be tested?\r\n\r\n#### Any background context you want to provide?\r\n\r\n#### What are the relevant tickets?\r\n[DDF-1336](https://codice.atlassian.net/browse/DDF-1336)\r\n#### Screenshots\r\n<!--(if appropriate)-->\r\n#### Checklist:\r\n- [ ] Documentation Updated\r\n- [ ] Update / Add Unit Tests\r\n- [ ] Update / Add Integration Tests\r\n\r\n#### Notes on Review Process\r\nPlease see [Notes on Review Process](https://codice.atlassian.net/wiki/spaces/DDF/pages/71946981/Pull+Request+Guidelines) for further guidance on requirements for merging and abbreviated reviews. \r\n\r\n#### Review Comment Legend:\r\n- ✏️ (Pencil) This comment is a nitpick or style suggestion, no action required for approval. This comment should provide a suggestion either as an in line code snippet or a gist. \r\n- ❓ (Question Mark) This comment is to gain a clearer understanding of design or code choices, clarification is required but action may not be necessary for approval.\r\n- ❗ (Exclamation Mark) This comment is critical and requires clarification or action before approval.\r\n","created_at":"2018-10-11T23:57:16Z","updated_at":"2018-10-22T21:50:57Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4a559a6fc44b3fd750d55d8a4a712d0c379ace4d","assignee":{"login":"pklinef","id":700518,"node_id":"MDQ6VXNlcjcwMDUxOA==","avatar_url":"https://avatars1.githubusercontent.com/u/700518?v=4","gravatar_id":"","url":"https://api.github.com/users/pklinef","html_url":"https://github.com/pklinef","followers_url":"https://api.github.com/users/pklinef/followers","following_url":"https://api.github.com/users/pklinef/following{/other_user}","gists_url":"https://api.github.com/users/pklinef/gists{/gist_id}","starred_url":"https://api.github.com/users/pklinef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pklinef/subscriptions","organizations_url":"https://api.github.com/users/pklinef/orgs","repos_url":"https://api.github.com/users/pklinef/repos","events_url":"https://api.github.com/users/pklinef/events{/privacy}","received_events_url":"https://api.github.com/users/pklinef/received_events","type":"User","site_admin":false},"assignees":[{"login":"pklinef","id":700518,"node_id":"MDQ6VXNlcjcwMDUxOA==","avatar_url":"https://avatars1.githubusercontent.com/u/700518?v=4","gravatar_id":"","url":"https://api.github.com/users/pklinef","html_url":"https://github.com/pklinef","followers_url":"https://api.github.com/users/pklinef/followers","following_url":"https://api.github.com/users/pklinef/following{/other_user}","gists_url":"https://api.github.com/users/pklinef/gists{/gist_id}","starred_url":"https://api.github.com/users/pklinef/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pklinef/subscriptions","organizations_url":"https://api.github.com/users/pklinef/orgs","repos_url":"https://api.github.com/users/pklinef/repos","events_url":"https://api.github.com/users/pklinef/events{/privacy}","received_events_url":"https://api.github.com/users/pklinef/received_events","type":"User","site_admin":false},{"login":"coyotesqrl","id":1469860,"node_id":"MDQ6VXNlcjE0Njk4NjA=","avatar_url":"https://avatars2.githubusercontent.com/u/1469860?v=4","gravatar_id":"","url":"https://api.github.com/users/coyotesqrl","html_url":"https://github.com/coyotesqrl","followers_url":"https://api.github.com/users/coyotesqrl/followers","following_url":"https://api.github.com/users/coyotesqrl/following{/other_user}","gists_url":"https://api.github.com/users/coyotesqrl/gists{/gist_id}","starred_url":"https://api.github.com/users/coyotesqrl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coyotesqrl/subscriptions","organizations_url":"https://api.github.com/users/coyotesqrl/orgs","repos_url":"https://api.github.com/users/coyotesqrl/repos","events_url":"https://api.github.com/users/coyotesqrl/events{/privacy}","received_events_url":"https://api.github.com/users/coyotesqrl/received_events","type":"User","site_admin":false},{"login":"clockard","id":5248090,"node_id":"MDQ6VXNlcjUyNDgwOTA=","avatar_url":"https://avatars1.githubusercontent.com/u/5248090?v=4","gravatar_id":"","url":"https://api.github.com/users/clockard","html_url":"https://github.com/clockard","followers_url":"https://api.github.com/users/clockard/followers","following_url":"https://api.github.com/users/clockard/following{/other_user}","gists_url":"https://api.github.com/users/clockard/gists{/gist_id}","starred_url":"https://api.github.com/users/clockard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/clockard/subscriptions","organizations_url":"https://api.github.com/users/clockard/orgs","repos_url":"https://api.github.com/users/clockard/repos","events_url":"https://api.github.com/users/clockard/events{/privacy}","received_events_url":"https://api.github.com/users/clockard/received_events","type":"User","site_admin":false},{"login":"lessarderic","id":11377558,"node_id":"MDQ6VXNlcjExMzc3NTU4","avatar_url":"https://avatars1.githubusercontent.com/u/11377558?v=4","gravatar_id":"","url":"https://api.github.com/users/lessarderic","html_url":"https://github.com/lessarderic","followers_url":"https://api.github.com/users/lessarderic/followers","following_url":"https://api.github.com/users/lessarderic/following{/other_user}","gists_url":"https://api.github.com/users/lessarderic/gists{/gist_id}","starred_url":"https://api.github.com/users/lessarderic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lessarderic/subscriptions","organizations_url":"https://api.github.com/users/lessarderic/orgs","repos_url":"https://api.github.com/users/lessarderic/repos","events_url":"https://api.github.com/users/lessarderic/events{/privacy}","received_events_url":"https://api.github.com/users/lessarderic/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"stustison","id":4391679,"node_id":"MDQ6VXNlcjQzOTE2Nzk=","avatar_url":"https://avatars0.githubusercontent.com/u/4391679?v=4","gravatar_id":"","url":"https://api.github.com/users/stustison","html_url":"https://github.com/stustison","followers_url":"https://api.github.com/users/stustison/followers","following_url":"https://api.github.com/users/stustison/following{/other_user}","gists_url":"https://api.github.com/users/stustison/gists{/gist_id}","starred_url":"https://api.github.com/users/stustison/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stustison/subscriptions","organizations_url":"https://api.github.com/users/stustison/orgs","repos_url":"https://api.github.com/users/stustison/repos","events_url":"https://api.github.com/users/stustison/events{/privacy}","received_events_url":"https://api.github.com/users/stustison/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":{"url":"https://api.github.com/repos/codice/ddf/milestones/24","html_url":"https://github.com/codice/ddf/milestone/24","labels_url":"https://api.github.com/repos/codice/ddf/milestones/24/labels","id":3673130,"node_id":"MDk6TWlsZXN0b25lMzY3MzEzMA==","number":24,"title":"2.13.3","description":"","creator":{"login":"clockard","id":5248090,"node_id":"MDQ6VXNlcjUyNDgwOTA=","avatar_url":"https://avatars1.githubusercontent.com/u/5248090?v=4","gravatar_id":"","url":"https://api.github.com/users/clockard","html_url":"https://github.com/clockard","followers_url":"https://api.github.com/users/clockard/followers","following_url":"https://api.github.com/users/clockard/following{/other_user}","gists_url":"https://api.github.com/users/clockard/gists{/gist_id}","starred_url":"https://api.github.com/users/clockard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/clockard/subscriptions","organizations_url":"https://api.github.com/users/clockard/orgs","repos_url":"https://api.github.com/users/clockard/repos","events_url":"https://api.github.com/users/clockard/events{/privacy}","received_events_url":"https://api.github.com/users/clockard/received_events","type":"User","site_admin":false},"open_issues":5,"closed_issues":21,"state":"open","created_at":"2018-09-20T03:35:54Z","updated_at":"2018-10-22T20:28:00Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/codice/ddf/pulls/3876/commits","review_comments_url":"https://api.github.com/repos/codice/ddf/pulls/3876/comments","review_comment_url":"https://api.github.com/repos/codice/ddf/pulls/comments{/number}","comments_url":"https://api.github.com/repos/codice/ddf/issues/3876/comments","statuses_url":"https://api.github.com/repos/codice/ddf/statuses/e391f7e0a1a2dc6c0a62d86d846090fdc8ff425a","head":{"label":"kcover:DDF-1366","ref":"DDF-1366","sha":"e391f7e0a1a2dc6c0a62d86d846090fdc8ff425a","user":{"login":"kcover","id":22778496,"node_id":"MDQ6VXNlcjIyNzc4NDk2","avatar_url":"https://avatars0.githubusercontent.com/u/22778496?v=4","gravatar_id":"","url":"https://api.github.com/users/kcover","html_url":"https://github.com/kcover","followers_url":"https://api.github.com/users/kcover/followers","following_url":"https://api.github.com/users/kcover/following{/other_user}","gists_url":"https://api.github.com/users/kcover/gists{/gist_id}","starred_url":"https://api.github.com/users/kcover/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kcover/subscriptions","organizations_url":"https://api.github.com/users/kcover/orgs","repos_url":"https://api.github.com/users/kcover/repos","events_url":"https://api.github.com/users/kcover/events{/privacy}","received_events_url":"https://api.github.com/users/kcover/received_events","type":"User","site_admin":false},"repo":{"id":116413576,"node_id":"MDEwOlJlcG9zaXRvcnkxMTY0MTM1NzY=","name":"ddf","full_name":"kcover/ddf","private":false,"owner":{"login":"kcover","id":22778496,"node_id":"MDQ6VXNlcjIyNzc4NDk2","avatar_url":"https://avatars0.githubusercontent.com/u/22778496?v=4","gravatar_id":"","url":"https://api.github.com/users/kcover","html_url":"https://github.com/kcover","followers_url":"https://api.github.com/users/kcover/followers","following_url":"https://api.github.com/users/kcover/following{/other_user}","gists_url":"https://api.github.com/users/kcover/gists{/gist_id}","starred_url":"https://api.github.com/users/kcover/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kcover/subscriptions","organizations_url":"https://api.github.com/users/kcover/orgs","repos_url":"https://api.github.com/users/kcover/repos","events_url":"https://api.github.com/users/kcover/events{/privacy}","received_events_url":"https://api.github.com/users/kcover/received_events","type":"User","site_admin":false},"html_url":"https://github.com/kcover/ddf","description":"DDF Distributed Data Framework - an open source, modular integration framework.","fork":true,"url":"https://api.github.com/repos/kcover/ddf","forks_url":"https://api.github.com/repos/kcover/ddf/forks","keys_url":"https://api.github.com/repos/kcover/ddf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kcover/ddf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kcover/ddf/teams","hooks_url":"https://api.github.com/repos/kcover/ddf/hooks","issue_events_url":"https://api.github.com/repos/kcover/ddf/issues/events{/number}","events_url":"https://api.github.com/repos/kcover/ddf/events","assignees_url":"https://api.github.com/repos/kcover/ddf/assignees{/user}","branches_url":"https://api.github.com/repos/kcover/ddf/branches{/branch}","tags_url":"https://api.github.com/repos/kcover/ddf/tags","blobs_url":"https://api.github.com/repos/kcover/ddf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kcover/ddf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kcover/ddf/git/refs{/sha}","trees_url":"https://api.github.com/repos/kcover/ddf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kcover/ddf/statuses/{sha}","languages_url":"https://api.github.com/repos/kcover/ddf/languages","stargazers_url":"https://api.github.com/repos/kcover/ddf/stargazers","contributors_url":"https://api.github.com/repos/kcover/ddf/contributors","subscribers_url":"https://api.github.com/repos/kcover/ddf/subscribers","subscription_url":"https://api.github.com/repos/kcover/ddf/subscription","commits_url":"https://api.github.com/repos/kcover/ddf/commits{/sha}","git_commits_url":"https://api.github.com/repos/kcover/ddf/git/commits{/sha}","comments_url":"https://api.github.com/repos/kcover/ddf/comments{/number}","issue_comment_url":"https://api.github.com/repos/kcover/ddf/issues/comments{/number}","contents_url":"https://api.github.com/repos/kcover/ddf/contents/{+path}","compare_url":"https://api.github.com/repos/kcover/ddf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kcover/ddf/merges","archive_url":"https://api.github.com/repos/kcover/ddf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kcover/ddf/downloads","issues_url":"https://api.github.com/repos/kcover/ddf/issues{/number}","pulls_url":"https://api.github.com/repos/kcover/ddf/pulls{/number}","milestones_url":"https://api.github.com/repos/kcover/ddf/milestones{/number}","notifications_url":"https://api.github.com/repos/kcover/ddf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kcover/ddf/labels{/name}","releases_url":"https://api.github.com/repos/kcover/ddf/releases{/id}","deployments_url":"https://api.github.com/repos/kcover/ddf/deployments","created_at":"2018-01-05T18:11:51Z","updated_at":"2018-01-05T18:12:12Z","pushed_at":"2018-10-22T21:18:36Z","git_url":"git://github.com/kcover/ddf.git","ssh_url":"git@github.com:kcover/ddf.git","clone_url":"https://github.com/kcover/ddf.git","svn_url":"https://github.com/kcover/ddf","homepage":"http://ddf.codice.org","size":388204,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"codice:2.13.x","ref":"2.13.x","sha":"3d8e3d2fb0cfc7337a970d014298efe6dabe2135","user":{"login":"codice","id":2386734,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIzODY3MzQ=","avatar_url":"https://avatars2.githubusercontent.com/u/2386734?v=4","gravatar_id":"","url":"https://api.github.com/users/codice","html_url":"https://github.com/codice","followers_url":"https://api.github.com/users/codice/followers","following_url":"https://api.github.com/users/codice/following{/other_user}","gists_url":"https://api.github.com/users/codice/gists{/gist_id}","starred_url":"https://api.github.com/users/codice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codice/subscriptions","organizations_url":"https://api.github.com/users/codice/orgs","repos_url":"https://api.github.com/users/codice/repos","events_url":"https://api.github.com/users/codice/events{/privacy}","received_events_url":"https://api.github.com/users/codice/received_events","type":"Organization","site_admin":false},"repo":{"id":15173563,"node_id":"MDEwOlJlcG9zaXRvcnkxNTE3MzU2Mw==","name":"ddf","full_name":"codice/ddf","private":false,"owner":{"login":"codice","id":2386734,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIzODY3MzQ=","avatar_url":"https://avatars2.githubusercontent.com/u/2386734?v=4","gravatar_id":"","url":"https://api.github.com/users/codice","html_url":"https://github.com/codice","followers_url":"https://api.github.com/users/codice/followers","following_url":"https://api.github.com/users/codice/following{/other_user}","gists_url":"https://api.github.com/users/codice/gists{/gist_id}","starred_url":"https://api.github.com/users/codice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codice/subscriptions","organizations_url":"https://api.github.com/users/codice/orgs","repos_url":"https://api.github.com/users/codice/repos","events_url":"https://api.github.com/users/codice/events{/privacy}","received_events_url":"https://api.github.com/users/codice/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/codice/ddf","description":"DDF Distributed Data Framework - an open source, modular integration framework.","fork":false,"url":"https://api.github.com/repos/codice/ddf","forks_url":"https://api.github.com/repos/codice/ddf/forks","keys_url":"https://api.github.com/repos/codice/ddf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/codice/ddf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/codice/ddf/teams","hooks_url":"https://api.github.com/repos/codice/ddf/hooks","issue_events_url":"https://api.github.com/repos/codice/ddf/issues/events{/number}","events_url":"https://api.github.com/repos/codice/ddf/events","assignees_url":"https://api.github.com/repos/codice/ddf/assignees{/user}","branches_url":"https://api.github.com/repos/codice/ddf/branches{/branch}","tags_url":"https://api.github.com/repos/codice/ddf/tags","blobs_url":"https://api.github.com/repos/codice/ddf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/codice/ddf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/codice/ddf/git/refs{/sha}","trees_url":"https://api.github.com/repos/codice/ddf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/codice/ddf/statuses/{sha}","languages_url":"https://api.github.com/repos/codice/ddf/languages","stargazers_url":"https://api.github.com/repos/codice/ddf/stargazers","contributors_url":"https://api.github.com/repos/codice/ddf/contributors","subscribers_url":"https://api.github.com/repos/codice/ddf/subscribers","subscription_url":"https://api.github.com/repos/codice/ddf/subscription","commits_url":"https://api.github.com/repos/codice/ddf/commits{/sha}","git_commits_url":"https://api.github.com/repos/codice/ddf/git/commits{/sha}","comments_url":"https://api.github.com/repos/codice/ddf/comments{/number}","issue_comment_url":"https://api.github.com/repos/codice/ddf/issues/comments{/number}","contents_url":"https://api.github.com/repos/codice/ddf/contents/{+path}","compare_url":"https://api.github.com/repos/codice/ddf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/codice/ddf/merges","archive_url":"https://api.github.com/repos/codice/ddf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/codice/ddf/downloads","issues_url":"https://api.github.com/repos/codice/ddf/issues{/number}","pulls_url":"https://api.github.com/repos/codice/ddf/pulls{/number}","milestones_url":"https://api.github.com/repos/codice/ddf/milestones{/number}","notifications_url":"https://api.github.com/repos/codice/ddf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/codice/ddf/labels{/name}","releases_url":"https://api.github.com/repos/codice/ddf/releases{/id}","deployments_url":"https://api.github.com/repos/codice/ddf/deployments","created_at":"2013-12-13T20:38:28Z","updated_at":"2018-10-22T20:19:30Z","pushed_at":"2018-10-22T21:36:45Z","git_url":"git://github.com/codice/ddf.git","ssh_url":"git@github.com:codice/ddf.git","clone_url":"https://github.com/codice/ddf.git","svn_url":"https://github.com/codice/ddf","homepage":"http://ddf.codice.org","size":412175,"stargazers_count":92,"watchers_count":92,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":156,"mirror_url":null,"archived":false,"open_issues_count":33,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":156,"open_issues":33,"watchers":92,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/codice/ddf/pulls/3876"},"html":{"href":"https://github.com/codice/ddf/pull/3876"},"issue":{"href":"https://api.github.com/repos/codice/ddf/issues/3876"},"comments":{"href":"https://api.github.com/repos/codice/ddf/issues/3876/comments"},"review_comments":{"href":"https://api.github.com/repos/codice/ddf/pulls/3876/comments"},"review_comment":{"href":"https://api.github.com/repos/codice/ddf/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/codice/ddf/pulls/3876/commits"},"statuses":{"href":"https://api.github.com/repos/codice/ddf/statuses/e391f7e0a1a2dc6c0a62d86d846090fdc8ff425a"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"kcover"}}