{"action":"created","comment":{"url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/comments/26532263","id":26532263,"diff_hunk":"@@ -0,0 +1,405 @@\n+package com.bazaarvoice.ostrich.pool;\n+\n+import com.bazaarvoice.ostrich.ServiceEndPoint;\n+import com.bazaarvoice.ostrich.ServiceFactory;\n+import com.bazaarvoice.ostrich.MultiThreadedServiceFactory;\n+import com.bazaarvoice.ostrich.metrics.Metrics;\n+import com.codahale.metrics.Counter;\n+import com.codahale.metrics.MetricRegistry;\n+import com.codahale.metrics.Timer;\n+import com.google.common.annotations.VisibleForTesting;\n+import com.google.common.collect.Maps;\n+import com.google.common.util.concurrent.ThreadFactoryBuilder;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import java.util.LinkedList;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.concurrent.Executors;\n+import java.util.concurrent.Future;\n+import java.util.concurrent.ScheduledExecutorService;\n+import java.util.concurrent.TimeUnit;\n+\n+import static com.google.common.base.Preconditions.checkArgument;\n+import static com.google.common.base.Preconditions.checkNotNull;\n+import static com.google.common.base.Preconditions.checkState;\n+\n+/**\n+ * A ServiceCache for \"heavy weight\" client instances, i.e. ones that are already thread safe.\n+ * Therefore unlike {@link com.bazaarvoice.ostrich.pool.SingleThreadedClientServiceCache}, we\n+ * can just map EndPoints to a single shared instance of a \"heavy weight\" client.\n+ *\n+ * This applies to third party client libraries for connecting to generic or specialized\n+ * services, i.e. {@code HttpClient}, {@code JestClient}, {@code ElasticSearchClient} etc.\n+ *\n+ * If your client library is multi-thread safe, this ServiceCache should provide better\n+ * performance than the {@link com.bazaarvoice.ostrich.pool.SingleThreadedClientServiceCache}.\n+ * @param <S> the Service type\n+ */\n+public class MultiThreadedClientServiceCache<S> implements ServiceCache<S> {\n+    private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedClientServiceCache.class);\n+    /**\n+     * We want to be able to perform more than 300 checkOuts and checkIns per second.\n+     * Thus we would like those methods to use an un-synchronized / non-blocking Map implementation.\n+     *\n+     * In order to do that we use a Copy-On-Write approach where all modifications\n+     * (new Endpoint, Endpoint goes away, etc) will create a new Map instance.  This is acceptable as\n+     * Endpoint addition / removal events are rare compared to checkIns and checkOuts.\n+     *\n+     * Because we are changing the pointer that is \"_instancesPerEndpoint\", we have to use the\n+     * volatile keyword to force any thread that wants to access the map, to pull it from\n+     * main memory.   There is a performance penalty associated with volatile, but it is\n+     * better than synchronization.\n+     */\n+    private volatile Map<ServiceEndPoint, HeavyServiceHandle<S>> _instancesPerEndpoint = Maps.newHashMap();\n+    private volatile boolean _isClosed;\n+\n+    // timers to report endPoint register & evict, and serviceFactory create & destroy events\n+    private final Metrics.InstanceMetrics _metrics;\n+    private final Timer _registerTimer;\n+    private final Timer _evictionTimer;\n+    private final Counter _serviceCounter;\n+\n+    // factory to deal with service handles\n+    private final ServiceFactory<S> _serviceFactory;\n+\n+    // time to wait before registering the same endpoint already created by other thread\n+    private static final long DUP_REGISTRATION_WINDOW_MILLIS = 1000l; // 1 second\n+\n+    // scheduled cleanup job executor\n+    private final Future<?> _cleanupFuture;\n+    private final ScheduledExecutorService _cleanupExecutor;\n+\n+    // default time in seconds before the scheduled job cleans up the deleted items\n+    private static final int DEFAULT_CLEANUP_DELAY_SECONDS = 15;\n+    private static final int DEFAULT_EVICTION_DELAY_SECONDS = 3 * 60; // 3 minutes\n+    private final int _evictionDelayInSeconds;\n+\n+    /**\n+     * Subclass of {@link com.bazaarvoice.ostrich.pool.ServiceHandle} that add methods & data\n+     * to allow for Eviction and freshness checks.\n+     */\n+    private class HeavyServiceHandle<T extends S> extends BasicServiceHandle<S> {\n+\n+        private final long _sellByDate;\n+        // volatile as we want all threads to make sure they know what this value is\n+        private volatile Long _expireAfterDate = null;\n+\n+        public HeavyServiceHandle( T service, ServiceEndPoint endPoint ) {\n+            super(service, endPoint);\n+\n+            _sellByDate = System.currentTimeMillis() + DUP_REGISTRATION_WINDOW_MILLIS;\n+        }\n+\n+        public boolean isOld() {\n+            return System.currentTimeMillis() > _sellByDate;\n+        }\n+\n+        public boolean hasBeenFlaggedForEviction() {\n+            return _expireAfterDate != null;\n+        }\n+\n+        public boolean timeToEvict() {\n+            return _expireAfterDate != null && System.currentTimeMillis() > _expireAfterDate;\n+        }\n+\n+        public void flagAsEvicted() {\n+            if (_expireAfterDate == null) {\n+                // Do not want multiple flagAsEvicted calls to keep resetting the time\n+                // Not synchronizing this as if multiple thread can set this, the time span will\n+                //  be dwarfed by the fact that the evictionDelay is measured in seconds.\n+                _expireAfterDate = System.currentTimeMillis() + (_evictionDelayInSeconds * 1000);\n+            }\n+        }\n+    }\n+\n+    /**\n+     * Provide a default {@link java.util.concurrent.ScheduledExecutorService} that is a thread pool of\n+     * size 1 in Daemon mode.\n+     */\n+    private static ScheduledExecutorService getDefaultExecutor() {\n+        return Executors.newScheduledThreadPool(1,\n+                new ThreadFactoryBuilder().setNameFormat(\"ServiceCache-CleanupThread-%d\").setDaemon(true).build());\n+    }\n+\n+    /**\n+     * Builds a {@code MultiThreadedClientServiceCache}. Used by the builder, used defaults\n+     * for {@code ScheduledExecutorService}, {@code evictionDelay} and {@code cleanupDelay}\n+     *\n+     * @param serviceFactory         the service factory for creating service handles\n+     * @param metricRegistry         the metric registry for reporting metrics\n+     */\n+    MultiThreadedClientServiceCache(MultiThreadedServiceFactory<S> serviceFactory, MetricRegistry metricRegistry) {\n+        this(serviceFactory, getDefaultExecutor(), DEFAULT_EVICTION_DELAY_SECONDS, DEFAULT_CLEANUP_DELAY_SECONDS, metricRegistry);\n+    }\n+\n+    /**\n+     * Builds a {@code MultiThreadedClientServiceCache}\n+     * This is the method used by our unit tests to construct MultiThreadedClientServiceCache instances with\n+     * super short eviction and cleanUp delays.\n+     *\n+     * @param serviceFactory         the service factory for creating service handles\n+     * @param executor               the executor for creating the eviction list (cache) cleanup thread\n+     * @param evictionDelayInSeconds how long to keep evicted handles around\n+     * @param cleanUpDelayInSeconds  how long to wait before scheduled cleanup\n+     * @param metricRegistry         the metric registry for reporting metrics\n+     */\n+    @VisibleForTesting\n+    MultiThreadedClientServiceCache(MultiThreadedServiceFactory<S> serviceFactory, ScheduledExecutorService executor,\n+                                    int evictionDelayInSeconds, int cleanUpDelayInSeconds, MetricRegistry metricRegistry) {\n+        checkNotNull(serviceFactory);\n+        checkNotNull(metricRegistry);\n+        checkArgument(evictionDelayInSeconds >= 0);\n+        checkArgument(cleanUpDelayInSeconds >= 0);\n+\n+        _serviceFactory = serviceFactory;\n+        _cleanupExecutor = executor;\n+        _evictionDelayInSeconds = evictionDelayInSeconds;\n+        _isClosed = false;\n+\n+        _metrics = Metrics.forInstance(metricRegistry, this, serviceFactory.getServiceName());\n+        _registerTimer = _metrics.timer(\"register-time\");\n+        _evictionTimer = _metrics.timer(\"eviction-time\");\n+        _serviceCounter = _metrics.counter(\"service-counter\");\n+\n+        _cleanupFuture = _cleanupExecutor.scheduleAtFixedRate(\n+            new Runnable() {\n+                @Override\n+                public void run() {\n+\n+                    List<HeavyServiceHandle<S>> handlesToDelete = new LinkedList<>();\n+\n+                    // Don't synchronize on just 'this', as 'this' is a anonymous inner Runnable class\n+                    // Instead synchronize on our parent class instance of MultiThreadedClientServiceCache so that\n+                    //  this code will be synchronized with evict() and doRegister().\n+                    synchronized (MultiThreadedClientServiceCache.this) {\n+\n+                        // Make a copy of the _instancesPerEndpoint copy on write map\n+                        Map<ServiceEndPoint, HeavyServiceHandle<S>> sourceCopy = Maps.newHashMap(_instancesPerEndpoint);\n+\n+                        // Purge evicted instances from the copy-on-write map\n+                        for (Map.Entry<ServiceEndPoint, HeavyServiceHandle<S>> entry : _instancesPerEndpoint.entrySet()) {\n+\n+                            HeavyServiceHandle handle = entry.getValue();\n+                            if ( handle.timeToEvict() ) {\n+                                handlesToDelete.add( sourceCopy.remove( handle.getEndPoint() ) );\n+                            }\n+                        }\n+                        _instancesPerEndpoint = sourceCopy;\n+                    }\n+\n+                    // Outside the synchronization loop, schedule the ServiceHandles for deletion\n+                    for ( HeavyServiceHandle<S> handle : handlesToDelete ) {\n+                        destroyService(handle);\n+                    }\n+                    _serviceCounter.dec(handlesToDelete.size());\n+                }\n+            },\n+            // In our unit tests we want to set the cleanup timeout to \"zero\", but executorService\n+            //  does not like that, so we set it to at least a millisecond.\n+            (cleanUpDelayInSeconds * 1000) + 1,\n+            (cleanUpDelayInSeconds * 1000) + 1,\n+            TimeUnit.MILLISECONDS);\n+    }\n+\n+    /**\n+     * Mimics the behavior of a cache check in, actually a NO-OP.\n+     * \n+     * Since there are no multiple service handle and their associated versions\n+     * to maintain, check in is free.\n+     *\n+     * @param handle The service handle that is being checked in.\n+     * @throws NullPointerException if the handle is null\n+     */\n+    @Override\n+    public void checkIn(ServiceHandle<S> handle) throws Exception {\n+        checkNotNull(handle);\n+    }\n+\n+    /**\n+     * Given an ServiceEndpoint return a ServiceHandle.\n+     *\n+     * If a ServiceHandle does not exist for the given ServiceEndpoint, this method will\n+     * synchronously create one and return it.\n+     *\n+     * @param endPoint The end point to retrieve the instance of service handle\n+     * @return the service handle\n+     * @throws IllegalStateException if the cache is closed\n+     * @throws NullPointerException if endpoint is null\n+     */\n+    @Override\n+    public ServiceHandle<S> checkOut(ServiceEndPoint endPoint) throws Exception {\n+        checkNotNull(endPoint);\n+        checkState(!_isClosed, \"cache is closed\");\n+        ServiceHandle<S> serviceHandle = _instancesPerEndpoint.get(endPoint);\n+        if (serviceHandle == null) {\n+            // This is the non-ideal state, as we not have to call a synchronized\n+            //  method to create the ServiceHandle and update the copy-on-write map.\n+            //\n+            // Not this can/will happen when new Endpoints are \"discovered\" due to the\n+            //  inherent race conditions in ServicePool and HostDiscovery.\n+            return doRegister(endPoint);\n+        }\n+\n+        // Note we are not checking if the serviceHandle has been flagged for Eviction, as\n+        //  there are race conditions between checkOut() and ServiceCache.evict().\n+        return serviceHandle;\n+    }\n+\n+    /**\n+     * Private registration method that is used by checkout() and register().\n+     *\n+     * @param endPoint the end point\n+     * @return the service handle\n+     */\n+    private ServiceHandle<S> doRegister(ServiceEndPoint endPoint) {\n+        checkNotNull(endPoint);\n+\n+        ServiceHandle<S> toDelete = null;\n+        ServiceHandle<S> toReturn;\n+\n+        synchronized ( this ) {\n+            HeavyServiceHandle<S> existingServiceHandle = _instancesPerEndpoint.get(endPoint);\n+            if ( existingServiceHandle == null || existingServiceHandle.hasBeenFlaggedForEviction() || existingServiceHandle.isOld() ) {\n+\n+                // If there was not an existingServiceHandle, then make a new one.\n+                //\n+                // If existingServiceHandle.hasBeenFlaggedForEviction() is true, that means this EndPoint\n+                //  has been \"evicted\" for being \"bad\" but has recovered before the Eviction timeout\n+                //  process has gotten around to cleaning up this serviceHandle.  In this case, we assume\n+                //  the \"safest\" thing to do is to create a new Client for that EndPoint, in case the\n+                //  problem was with the \"old\" client.\n+                //\n+                // If the existingServiceHandle is \"new\" don't create a new client objects due to the\n+                //  race condition in HostDiscovery and ServicePool, which can cause a checkOut() to\n+                //  occur before it's associated ServiceCache.register().\n+                // Thus we want have a short period of time where duplicate \"checkouts\" and a register\n+                //  will not thrash the system creating a series of Client instances.\n+                //\n+                // _serviceFactory.create(endPoint) is a potentially expensive operation, memory, file handles, etc.\n+                // hence we really only want to do it when we have to, preferably via the out-of-band\n+                // ServiceCache.register() method, instead of the high traffic checkOut method.\n+                S service = _serviceFactory.create(endPoint);\n+                _serviceCounter.inc();\n+\n+                HeavyServiceHandle<S> newServiceHandle = new HeavyServiceHandle<>(service, endPoint);\n+\n+                // add the newServiceHandle to a new copy of the _instancesPerEndpoint map\n+                Map<ServiceEndPoint, HeavyServiceHandle<S>> sourceCopy = Maps.newHashMap(_instancesPerEndpoint);\n+                sourceCopy.put(endPoint, newServiceHandle);\n+                _instancesPerEndpoint = sourceCopy;\n+\n+                if (existingServiceHandle != null) {\n+                    toDelete = existingServiceHandle;\n+                }\n+                toReturn = newServiceHandle;\n+            }\n+            else {","path":"core/src/main/java/com/bazaarvoice/ostrich/pool/MultiThreadedClientServiceCache.java","position":298,"original_position":298,"commit_id":"7718946c0d3c12ea0cae2c63b4342dfa2fe6da92","original_commit_id":"7718946c0d3c12ea0cae2c63b4342dfa2fe6da92","user":{"login":"dump247","id":448969,"avatar_url":"https://avatars.githubusercontent.com/u/448969?v=3","gravatar_id":"","url":"https://api.github.com/users/dump247","html_url":"https://github.com/dump247","followers_url":"https://api.github.com/users/dump247/followers","following_url":"https://api.github.com/users/dump247/following{/other_user}","gists_url":"https://api.github.com/users/dump247/gists{/gist_id}","starred_url":"https://api.github.com/users/dump247/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dump247/subscriptions","organizations_url":"https://api.github.com/users/dump247/orgs","repos_url":"https://api.github.com/users/dump247/repos","events_url":"https://api.github.com/users/dump247/events{/privacy}","received_events_url":"https://api.github.com/users/dump247/received_events","type":"User","site_admin":false},"body":"Project conventions: else goes on same line as ending brace","created_at":"2015-03-16T22:03:40Z","updated_at":"2015-03-16T22:03:40Z","html_url":"https://github.com/bazaarvoice/ostrich/pull/170#discussion_r26532263","pull_request_url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170","_links":{"self":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/comments/26532263"},"html":{"href":"https://github.com/bazaarvoice/ostrich/pull/170#discussion_r26532263"},"pull_request":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170"}}},"pull_request":{"url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170","id":30979754,"html_url":"https://github.com/bazaarvoice/ostrich/pull/170","diff_url":"https://github.com/bazaarvoice/ostrich/pull/170.diff","patch_url":"https://github.com/bazaarvoice/ostrich/pull/170.patch","issue_url":"https://api.github.com/repos/bazaarvoice/ostrich/issues/170","number":170,"state":"open","locked":false,"title":"Implement MultiThreaded and SingleThreaded service cache","user":{"login":"bivasdas","id":5587198,"avatar_url":"https://avatars.githubusercontent.com/u/5587198?v=3","gravatar_id":"","url":"https://api.github.com/users/bivasdas","html_url":"https://github.com/bivasdas","followers_url":"https://api.github.com/users/bivasdas/followers","following_url":"https://api.github.com/users/bivasdas/following{/other_user}","gists_url":"https://api.github.com/users/bivasdas/gists{/gist_id}","starred_url":"https://api.github.com/users/bivasdas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bivasdas/subscriptions","organizations_url":"https://api.github.com/users/bivasdas/orgs","repos_url":"https://api.github.com/users/bivasdas/repos","events_url":"https://api.github.com/users/bivasdas/events{/privacy}","received_events_url":"https://api.github.com/users/bivasdas/received_events","type":"User","site_admin":false},"body":"\t  Created SingleThreaded and MultiThreaded service cache\r\n\t  Simplified, we just have a copy-on-write map and a single cleanup thread now\r\n\t  Added marker interface to force a specific type of service factory in multi thread cache\r\n\t  Updated perf-test-suite to use new multi threaded cache\r\n\t  Updated test suite with more metrics, added chaos worked to create chaos while test run\r\n\t  Updatd service pool test to test both cache\r\n\t  Fix all the docs","created_at":"2015-03-11T20:03:25Z","updated_at":"2015-03-16T22:03:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"53a61f8ffc421bcc13d153ebe70d0c7dab42df39","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170/commits","review_comments_url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170/comments","review_comment_url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/comments{/number}","comments_url":"https://api.github.com/repos/bazaarvoice/ostrich/issues/170/comments","statuses_url":"https://api.github.com/repos/bazaarvoice/ostrich/statuses/7718946c0d3c12ea0cae2c63b4342dfa2fe6da92","head":{"label":"bivasdas:new-service-cache","ref":"new-service-cache","sha":"7718946c0d3c12ea0cae2c63b4342dfa2fe6da92","user":{"login":"bivasdas","id":5587198,"avatar_url":"https://avatars.githubusercontent.com/u/5587198?v=3","gravatar_id":"","url":"https://api.github.com/users/bivasdas","html_url":"https://github.com/bivasdas","followers_url":"https://api.github.com/users/bivasdas/followers","following_url":"https://api.github.com/users/bivasdas/following{/other_user}","gists_url":"https://api.github.com/users/bivasdas/gists{/gist_id}","starred_url":"https://api.github.com/users/bivasdas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bivasdas/subscriptions","organizations_url":"https://api.github.com/users/bivasdas/orgs","repos_url":"https://api.github.com/users/bivasdas/repos","events_url":"https://api.github.com/users/bivasdas/events{/privacy}","received_events_url":"https://api.github.com/users/bivasdas/received_events","type":"User","site_admin":false},"repo":{"id":25937888,"name":"ostrich","full_name":"bivasdas/ostrich","owner":{"login":"bivasdas","id":5587198,"avatar_url":"https://avatars.githubusercontent.com/u/5587198?v=3","gravatar_id":"","url":"https://api.github.com/users/bivasdas","html_url":"https://github.com/bivasdas","followers_url":"https://api.github.com/users/bivasdas/followers","following_url":"https://api.github.com/users/bivasdas/following{/other_user}","gists_url":"https://api.github.com/users/bivasdas/gists{/gist_id}","starred_url":"https://api.github.com/users/bivasdas/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bivasdas/subscriptions","organizations_url":"https://api.github.com/users/bivasdas/orgs","repos_url":"https://api.github.com/users/bivasdas/repos","events_url":"https://api.github.com/users/bivasdas/events{/privacy}","received_events_url":"https://api.github.com/users/bivasdas/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bivasdas/ostrich","description":"SOA Library","fork":true,"url":"https://api.github.com/repos/bivasdas/ostrich","forks_url":"https://api.github.com/repos/bivasdas/ostrich/forks","keys_url":"https://api.github.com/repos/bivasdas/ostrich/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bivasdas/ostrich/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bivasdas/ostrich/teams","hooks_url":"https://api.github.com/repos/bivasdas/ostrich/hooks","issue_events_url":"https://api.github.com/repos/bivasdas/ostrich/issues/events{/number}","events_url":"https://api.github.com/repos/bivasdas/ostrich/events","assignees_url":"https://api.github.com/repos/bivasdas/ostrich/assignees{/user}","branches_url":"https://api.github.com/repos/bivasdas/ostrich/branches{/branch}","tags_url":"https://api.github.com/repos/bivasdas/ostrich/tags","blobs_url":"https://api.github.com/repos/bivasdas/ostrich/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bivasdas/ostrich/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bivasdas/ostrich/git/refs{/sha}","trees_url":"https://api.github.com/repos/bivasdas/ostrich/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bivasdas/ostrich/statuses/{sha}","languages_url":"https://api.github.com/repos/bivasdas/ostrich/languages","stargazers_url":"https://api.github.com/repos/bivasdas/ostrich/stargazers","contributors_url":"https://api.github.com/repos/bivasdas/ostrich/contributors","subscribers_url":"https://api.github.com/repos/bivasdas/ostrich/subscribers","subscription_url":"https://api.github.com/repos/bivasdas/ostrich/subscription","commits_url":"https://api.github.com/repos/bivasdas/ostrich/commits{/sha}","git_commits_url":"https://api.github.com/repos/bivasdas/ostrich/git/commits{/sha}","comments_url":"https://api.github.com/repos/bivasdas/ostrich/comments{/number}","issue_comment_url":"https://api.github.com/repos/bivasdas/ostrich/issues/comments{/number}","contents_url":"https://api.github.com/repos/bivasdas/ostrich/contents/{+path}","compare_url":"https://api.github.com/repos/bivasdas/ostrich/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bivasdas/ostrich/merges","archive_url":"https://api.github.com/repos/bivasdas/ostrich/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bivasdas/ostrich/downloads","issues_url":"https://api.github.com/repos/bivasdas/ostrich/issues{/number}","pulls_url":"https://api.github.com/repos/bivasdas/ostrich/pulls{/number}","milestones_url":"https://api.github.com/repos/bivasdas/ostrich/milestones{/number}","notifications_url":"https://api.github.com/repos/bivasdas/ostrich/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bivasdas/ostrich/labels{/name}","releases_url":"https://api.github.com/repos/bivasdas/ostrich/releases{/id}","created_at":"2014-10-29T19:35:15Z","updated_at":"2015-02-24T20:14:46Z","pushed_at":"2015-03-16T18:14:08Z","git_url":"git://github.com/bivasdas/ostrich.git","ssh_url":"git@github.com:bivasdas/ostrich.git","clone_url":"https://github.com/bivasdas/ostrich.git","svn_url":"https://github.com/bivasdas/ostrich","homepage":"","size":1831,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"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":"bazaarvoice:master","ref":"master","sha":"a4efa5a313cf9a753b6b9d64f41f1ac591e03783","user":{"login":"bazaarvoice","id":152469,"avatar_url":"https://avatars.githubusercontent.com/u/152469?v=3","gravatar_id":"","url":"https://api.github.com/users/bazaarvoice","html_url":"https://github.com/bazaarvoice","followers_url":"https://api.github.com/users/bazaarvoice/followers","following_url":"https://api.github.com/users/bazaarvoice/following{/other_user}","gists_url":"https://api.github.com/users/bazaarvoice/gists{/gist_id}","starred_url":"https://api.github.com/users/bazaarvoice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bazaarvoice/subscriptions","organizations_url":"https://api.github.com/users/bazaarvoice/orgs","repos_url":"https://api.github.com/users/bazaarvoice/repos","events_url":"https://api.github.com/users/bazaarvoice/events{/privacy}","received_events_url":"https://api.github.com/users/bazaarvoice/received_events","type":"Organization","site_admin":false},"repo":{"id":4341250,"name":"ostrich","full_name":"bazaarvoice/ostrich","owner":{"login":"bazaarvoice","id":152469,"avatar_url":"https://avatars.githubusercontent.com/u/152469?v=3","gravatar_id":"","url":"https://api.github.com/users/bazaarvoice","html_url":"https://github.com/bazaarvoice","followers_url":"https://api.github.com/users/bazaarvoice/followers","following_url":"https://api.github.com/users/bazaarvoice/following{/other_user}","gists_url":"https://api.github.com/users/bazaarvoice/gists{/gist_id}","starred_url":"https://api.github.com/users/bazaarvoice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bazaarvoice/subscriptions","organizations_url":"https://api.github.com/users/bazaarvoice/orgs","repos_url":"https://api.github.com/users/bazaarvoice/repos","events_url":"https://api.github.com/users/bazaarvoice/events{/privacy}","received_events_url":"https://api.github.com/users/bazaarvoice/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/bazaarvoice/ostrich","description":"SOA Library","fork":false,"url":"https://api.github.com/repos/bazaarvoice/ostrich","forks_url":"https://api.github.com/repos/bazaarvoice/ostrich/forks","keys_url":"https://api.github.com/repos/bazaarvoice/ostrich/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bazaarvoice/ostrich/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bazaarvoice/ostrich/teams","hooks_url":"https://api.github.com/repos/bazaarvoice/ostrich/hooks","issue_events_url":"https://api.github.com/repos/bazaarvoice/ostrich/issues/events{/number}","events_url":"https://api.github.com/repos/bazaarvoice/ostrich/events","assignees_url":"https://api.github.com/repos/bazaarvoice/ostrich/assignees{/user}","branches_url":"https://api.github.com/repos/bazaarvoice/ostrich/branches{/branch}","tags_url":"https://api.github.com/repos/bazaarvoice/ostrich/tags","blobs_url":"https://api.github.com/repos/bazaarvoice/ostrich/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bazaarvoice/ostrich/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bazaarvoice/ostrich/git/refs{/sha}","trees_url":"https://api.github.com/repos/bazaarvoice/ostrich/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bazaarvoice/ostrich/statuses/{sha}","languages_url":"https://api.github.com/repos/bazaarvoice/ostrich/languages","stargazers_url":"https://api.github.com/repos/bazaarvoice/ostrich/stargazers","contributors_url":"https://api.github.com/repos/bazaarvoice/ostrich/contributors","subscribers_url":"https://api.github.com/repos/bazaarvoice/ostrich/subscribers","subscription_url":"https://api.github.com/repos/bazaarvoice/ostrich/subscription","commits_url":"https://api.github.com/repos/bazaarvoice/ostrich/commits{/sha}","git_commits_url":"https://api.github.com/repos/bazaarvoice/ostrich/git/commits{/sha}","comments_url":"https://api.github.com/repos/bazaarvoice/ostrich/comments{/number}","issue_comment_url":"https://api.github.com/repos/bazaarvoice/ostrich/issues/comments{/number}","contents_url":"https://api.github.com/repos/bazaarvoice/ostrich/contents/{+path}","compare_url":"https://api.github.com/repos/bazaarvoice/ostrich/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bazaarvoice/ostrich/merges","archive_url":"https://api.github.com/repos/bazaarvoice/ostrich/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bazaarvoice/ostrich/downloads","issues_url":"https://api.github.com/repos/bazaarvoice/ostrich/issues{/number}","pulls_url":"https://api.github.com/repos/bazaarvoice/ostrich/pulls{/number}","milestones_url":"https://api.github.com/repos/bazaarvoice/ostrich/milestones{/number}","notifications_url":"https://api.github.com/repos/bazaarvoice/ostrich/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bazaarvoice/ostrich/labels{/name}","releases_url":"https://api.github.com/repos/bazaarvoice/ostrich/releases{/id}","created_at":"2012-05-15T22:13:54Z","updated_at":"2015-02-24T19:36:53Z","pushed_at":"2015-02-24T19:36:53Z","git_url":"git://github.com/bazaarvoice/ostrich.git","ssh_url":"git@github.com:bazaarvoice/ostrich.git","clone_url":"https://github.com/bazaarvoice/ostrich.git","svn_url":"https://github.com/bazaarvoice/ostrich","homepage":"","size":2463,"stargazers_count":21,"watchers_count":21,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":19,"mirror_url":null,"open_issues_count":12,"forks":19,"open_issues":12,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170"},"html":{"href":"https://github.com/bazaarvoice/ostrich/pull/170"},"issue":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/issues/170"},"comments":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/issues/170/comments"},"review_comments":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170/comments"},"review_comment":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/pulls/170/commits"},"statuses":{"href":"https://api.github.com/repos/bazaarvoice/ostrich/statuses/7718946c0d3c12ea0cae2c63b4342dfa2fe6da92"}}}}