{"action":"created","comment":{"url":"https://api.github.com/repos/eclipse/smarthome/pulls/comments/35299336","id":35299336,"diff_hunk":"@@ -0,0 +1,247 @@\n+/**\n+ * Copyright (c) 2014-2015 openHAB UG (haftungsbeschraenkt) and others.\n+ * All rights reserved. This program and the accompanying materials\n+ * are made available under the terms of the Eclipse Public License v1.0\n+ * which accompanies this distribution, and is available at\n+ * http://www.eclipse.org/legal/epl-v10.html\n+ */\n+package org.eclipse.smarthome.core.common;\n+\n+import java.util.Map;\n+import java.util.Map.Entry;\n+import java.util.WeakHashMap;\n+import java.util.concurrent.ArrayBlockingQueue;\n+import java.util.concurrent.ConcurrentHashMap;\n+import java.util.concurrent.ExecutorService;\n+import java.util.concurrent.Executors;\n+import java.util.concurrent.RejectedExecutionHandler;\n+import java.util.concurrent.ScheduledExecutorService;\n+import java.util.concurrent.ThreadFactory;\n+import java.util.concurrent.ThreadPoolExecutor;\n+import java.util.concurrent.TimeUnit;\n+import java.util.concurrent.atomic.AtomicInteger;\n+\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+/**\n+ * <p>\n+ * This class provides a general mechanism to create thread pools. In general, no code of Eclipse SmartHome\n+ * should deal with its own pools, but rather use this class.\n+ * The created thread pools have named threads, so that it is easy to find them in the debugger. Additionally, it is\n+ * possible to configure the pool sizes through the configuration admin service, so that solutions have the chance to\n+ * tweak the pool sizes according to their needs.\n+ * </p>\n+ * <p>\n+ * The configuration can be done as\n+ * <br/>\n+ * {@code org.eclipse.smarthome.threadpool:<poolName>=<poolSize>[,<minSize>]}\n+ * <br/>\n+ * where minSize is only applicable for non-scheduled thread pools and is the number of threads to keep, even if idle.\n+ * All other threads will time out after {@link THREAD_TIMEOUT}.\n+ * </p>\n+ * <p>\n+ * All non-scheduled pools are created with maximum queue size of {@link MAX_THREAD_QUEUE_SIZE}.\n+ * </p>\n+ *\n+ * @author Kai Kreuzer - Initial contribution\n+ *\n+ */\n+public class ThreadPoolManager {\n+\n+    private final static Logger logger = LoggerFactory.getLogger(ThreadPoolManager.class);\n+\n+    private static final int DEFAULT_THREAD_POOL_SIZE = 3;\n+    private static final int DEFAULT_THREAD_POOL_CORE_SIZE = 1;\n+    private static final int MAX_THREAD_QUEUE_SIZE = 1000;\n+\n+    private static final long THREAD_TIMEOUT = 65L;\n+\n+    static private Map<String, ExecutorService> pools = new WeakHashMap<>();\n+\n+    static private Map<String, int[]> configs = new ConcurrentHashMap<>();\n+\n+    protected void activate(Map<String, Object> properties) {\n+        modified(properties);\n+    }\n+\n+    protected void modified(Map<String, Object> properties) {\n+        for (Entry<String, Object> entry : properties.entrySet()) {\n+            if (entry.getKey().equals(\"service.pid\") || entry.getKey().equals(\"component.name\"))\n+                continue;\n+            String poolName = entry.getKey();\n+            Object config = entry.getValue();\n+            if (config == null) {\n+                configs.remove(poolName);\n+            }\n+            if (config instanceof String) {\n+                String[] segments = ((String) config).split(\",\");\n+                if (segments.length > 2) {\n+                    logger.warn(\"Ignoring invalid configuration for pool '{}': {} - config have at most 2 parts\",\n+                            new Object[] { poolName, config });\n+                    continue;\n+                }\n+                try {\n+                    int[] cfg = (segments.length == 1)\n+                            ? new int[] { Integer.valueOf(segments[0]), DEFAULT_THREAD_POOL_CORE_SIZE }\n+                            : new int[] { Integer.valueOf(segments[0]), Integer.valueOf(segments[1]) };\n+                    if (cfg[0] < cfg[1]) {\n+                        logger.warn(\n+                                \"Ignoring invalid configuration for pool '{}': {} - max value must be bigger than min value\",\n+                                new Object[] { poolName, config });\n+                        continue;\n+                    }\n+                    if (cfg[0] < 0 || cfg[1] < 0) {\n+                        logger.warn(\"Ignoring invalid configuration for pool '{}': {} - value must not be negative\",\n+                                new Object[] { poolName, config });\n+                        continue;\n+                    }\n+                    configs.put(poolName, cfg);\n+                    ThreadPoolExecutor pool = (ThreadPoolExecutor) pools.get(poolName);\n+                    if (pool != null) {\n+                        if (pool instanceof ScheduledExecutorService) {\n+                            // we only need to set the core pool size here\n+                            pool.setCorePoolSize(cfg[0]);\n+                            logger.debug(\"Updated scheduled thread pool '{}' to size {}\",\n+                                    new Object[] { poolName, cfg[0] });\n+                        } else {\n+                            // the configured pool size is the max size\n+                            pool.setMaximumPoolSize(cfg[0]);\n+                            pool.setCorePoolSize(cfg[1]);\n+                            logger.debug(\"Updated thread pool '{}' to size {}-{}\",\n+                                    new Object[] { poolName, cfg[1], cfg[0] });\n+                        }\n+                    }\n+                } catch (NumberFormatException e) {\n+                    logger.warn(\"Ignoring invalid configuration for pool '{}': {} - entries must be integer values\",\n+                            new Object[] { poolName, config });\n+                    continue;\n+                }\n+            }\n+        }\n+    }\n+\n+    /**\n+     * Returns an instance of a scheduled thread pool service. If it is the first request for the given pool name, the\n+     * instance is newly created.\n+     *\n+     * @param poolName a short name used to identify the pool, e.g. \"discovery\"\n+     * @return an instance to use\n+     */\n+    static public ScheduledExecutorService getScheduledPool(String poolName) {\n+        ExecutorService pool = pools.get(poolName);\n+        if (pool == null) {\n+            synchronized (pools) {\n+                // do a double check if it is still null or if another thread might have created it meanwhile\n+                pool = pools.get(poolName);\n+                if (pool == null) {\n+                    int[] cfg = getConfig(poolName);\n+                    pool = Executors.newScheduledThreadPool(cfg[0], new NamedThreadFactory(poolName));\n+                    ((ThreadPoolExecutor) pool).setKeepAliveTime(THREAD_TIMEOUT, TimeUnit.SECONDS);\n+                    ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true);\n+                    pools.put(poolName, pool);\n+                    logger.debug(\"Created scheduled thread pool '{}' of size {}\", new Object[] { poolName, cfg[0] });\n+                }\n+            }\n+        }\n+        if (pool instanceof ScheduledExecutorService) {\n+            return (ScheduledExecutorService) pool;\n+        } else {\n+            throw new IllegalArgumentException(\"Pool \" + poolName + \" is not a scheduled pool!\");\n+        }\n+    }\n+\n+    /**\n+     * Returns an instance of a cached thread pool service. If it is the first request for the given pool name, the\n+     * instance is newly created.\n+     *\n+     * @param poolName a short name used to identify the pool, e.g. \"discovery\"\n+     * @return an instance to use\n+     */\n+    static public ExecutorService getPool(String poolName) {\n+        ExecutorService pool = pools.get(poolName);\n+        if (pool == null) {\n+            synchronized (pools) {\n+                // do a double check if it is still null or if another thread might have created it meanwhile\n+                pool = pools.get(poolName);\n+                if (pool == null) {\n+                    int[] cfg = getConfig(poolName);\n+                    pool = new CommonThreadExecutor(poolName, cfg[1], cfg[0], MAX_THREAD_QUEUE_SIZE);\n+                    pools.put(poolName, pool);\n+                    logger.debug(\"Created thread pool '{}' with size {}-{}\", new Object[] { poolName, cfg[1], cfg[0] });\n+                }\n+            }\n+        }\n+        return pool;\n+    }\n+\n+    private static int[] getConfig(String poolName) {\n+        int[] cfg = configs.get(poolName);\n+        return (cfg != null) ? cfg : new int[] { DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_CORE_SIZE };\n+    }\n+\n+    private static class CommonThreadExecutor extends ThreadPoolExecutor {\n+\n+        public CommonThreadExecutor(final String poolName, int corePoolSize, int maxPoolSize, int maxQueueSize) {\n+            this(poolName, corePoolSize, maxPoolSize, maxQueueSize, new NamedThreadFactory(poolName),\n+                    new ThreadPoolExecutor.DiscardPolicy() {\n+                        // The pool is bounded and rejections will happen during shutdown\n+                        @Override\n+                        public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {\n+                            // Log and discard\n+                            logger.warn(\"Thread pool '{}' rejected execution of {}\",\n+                                    new Object[] { poolName, runnable.getClass() });\n+                            super.rejectedExecution(runnable, threadPoolExecutor);\n+                        }\n+                    });\n+        }\n+\n+        public CommonThreadExecutor(String threadPool, int corePoolSize, int maxPoolSize, int maxQueueSize,\n+                ThreadFactory threadFactory, RejectedExecutionHandler rejectedHandler) {\n+            // This is the same as Executors.newCachedThreadPool\n+            super(corePoolSize, maxPoolSize, THREAD_TIMEOUT, TimeUnit.SECONDS,\n+                    new ArrayBlockingQueue<Runnable>(maxQueueSize), threadFactory, rejectedHandler);\n+        }\n+\n+        @Override\n+        protected void afterExecute(Runnable runnable, Throwable throwable) {\n+            super.afterExecute(runnable, throwable);\n+            if (throwable != null) {\n+                Throwable cause = throwable.getCause();\n+                if (cause instanceof InterruptedException) {\n+                    // Ignore this, might happen when we shutdownNow() the executor. We can't\n+                    // log at this point as the logging system might be stopped already.\n+                    return;\n+                }\n+            }\n+        }\n+    }\n+\n+    /**\n+     * This is a normal thread factory, which adds a named prefix to all created threads.\n+     */\n+    private static class NamedThreadFactory implements ThreadFactory {\n+\n+        protected final ThreadGroup group;\n+        protected final AtomicInteger threadNumber = new AtomicInteger(1);\n+        protected final String namePrefix;\n+\n+        public NamedThreadFactory(String threadPool) {\n+            this.namePrefix = \"ESH-\" + threadPool + \"-\";\n+            SecurityManager s = System.getSecurityManager();\n+            group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();\n+        }\n+\n+        @Override\n+        public Thread newThread(Runnable r) {\n+            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);\n+            if (t.isDaemon())\n+                t.setDaemon(false);","path":"bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/common/ThreadPoolManager.java","position":239,"original_position":239,"commit_id":"0d7a5c062a1765fe0ce3b1303e4da9fc09446f34","original_commit_id":"0d7a5c062a1765fe0ce3b1303e4da9fc09446f34","user":{"login":"kgoderis","id":5459062,"avatar_url":"https://avatars.githubusercontent.com/u/5459062?v=3","gravatar_id":"","url":"https://api.github.com/users/kgoderis","html_url":"https://github.com/kgoderis","followers_url":"https://api.github.com/users/kgoderis/followers","following_url":"https://api.github.com/users/kgoderis/following{/other_user}","gists_url":"https://api.github.com/users/kgoderis/gists{/gist_id}","starred_url":"https://api.github.com/users/kgoderis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kgoderis/subscriptions","organizations_url":"https://api.github.com/users/kgoderis/orgs","repos_url":"https://api.github.com/users/kgoderis/repos","events_url":"https://api.github.com/users/kgoderis/events{/privacy}","received_events_url":"https://api.github.com/users/kgoderis/received_events","type":"User","site_admin":false},"body":"Why not use Daemon threads? at least they prevent the JVM from blocking at the exit. if the thread pool manager is used by other parts of the core or bindings, and if the bindings are a bit dodgy, at least we know that it won't hurt the JVM... no?","created_at":"2015-07-23T08:25:06Z","updated_at":"2015-07-23T08:25:06Z","html_url":"https://github.com/eclipse/smarthome/pull/387#discussion_r35299336","pull_request_url":"https://api.github.com/repos/eclipse/smarthome/pulls/387","_links":{"self":{"href":"https://api.github.com/repos/eclipse/smarthome/pulls/comments/35299336"},"html":{"href":"https://github.com/eclipse/smarthome/pull/387#discussion_r35299336"},"pull_request":{"href":"https://api.github.com/repos/eclipse/smarthome/pulls/387"}}},"pull_request":{"url":"https://api.github.com/repos/eclipse/smarthome/pulls/387","id":40222431,"html_url":"https://github.com/eclipse/smarthome/pull/387","diff_url":"https://github.com/eclipse/smarthome/pull/387.diff","patch_url":"https://github.com/eclipse/smarthome/pull/387.patch","issue_url":"https://api.github.com/repos/eclipse/smarthome/issues/387","number":387,"state":"closed","locked":false,"title":"introduced named and configurable thread pools","user":{"login":"kaikreuzer","id":3244965,"avatar_url":"https://avatars.githubusercontent.com/u/3244965?v=3","gravatar_id":"","url":"https://api.github.com/users/kaikreuzer","html_url":"https://github.com/kaikreuzer","followers_url":"https://api.github.com/users/kaikreuzer/followers","following_url":"https://api.github.com/users/kaikreuzer/following{/other_user}","gists_url":"https://api.github.com/users/kaikreuzer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaikreuzer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaikreuzer/subscriptions","organizations_url":"https://api.github.com/users/kaikreuzer/orgs","repos_url":"https://api.github.com/users/kaikreuzer/repos","events_url":"https://api.github.com/users/kaikreuzer/events{/privacy}","received_events_url":"https://api.github.com/users/kaikreuzer/received_events","type":"User","site_admin":false},"body":"Bug: 471763\r\nSigned-off-by: Kai Kreuzer <kai@openhab.org>","created_at":"2015-07-17T11:26:09Z","updated_at":"2015-07-23T08:25:06Z","closed_at":"2015-07-23T07:30:46Z","merged_at":"2015-07-23T07:30:46Z","merge_commit_sha":"25fd45cc43aea43c320345b4c2cdaa2c8e8c996f","assignee":{"login":"dnobel","id":1380535,"avatar_url":"https://avatars.githubusercontent.com/u/1380535?v=3","gravatar_id":"","url":"https://api.github.com/users/dnobel","html_url":"https://github.com/dnobel","followers_url":"https://api.github.com/users/dnobel/followers","following_url":"https://api.github.com/users/dnobel/following{/other_user}","gists_url":"https://api.github.com/users/dnobel/gists{/gist_id}","starred_url":"https://api.github.com/users/dnobel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dnobel/subscriptions","organizations_url":"https://api.github.com/users/dnobel/orgs","repos_url":"https://api.github.com/users/dnobel/repos","events_url":"https://api.github.com/users/dnobel/events{/privacy}","received_events_url":"https://api.github.com/users/dnobel/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/eclipse/smarthome/pulls/387/commits","review_comments_url":"https://api.github.com/repos/eclipse/smarthome/pulls/387/comments","review_comment_url":"https://api.github.com/repos/eclipse/smarthome/pulls/comments{/number}","comments_url":"https://api.github.com/repos/eclipse/smarthome/issues/387/comments","statuses_url":"https://api.github.com/repos/eclipse/smarthome/statuses/0d7a5c062a1765fe0ce3b1303e4da9fc09446f34","head":{"label":"kaikreuzer:threadpools","ref":"threadpools","sha":"0d7a5c062a1765fe0ce3b1303e4da9fc09446f34","user":{"login":"kaikreuzer","id":3244965,"avatar_url":"https://avatars.githubusercontent.com/u/3244965?v=3","gravatar_id":"","url":"https://api.github.com/users/kaikreuzer","html_url":"https://github.com/kaikreuzer","followers_url":"https://api.github.com/users/kaikreuzer/followers","following_url":"https://api.github.com/users/kaikreuzer/following{/other_user}","gists_url":"https://api.github.com/users/kaikreuzer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaikreuzer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaikreuzer/subscriptions","organizations_url":"https://api.github.com/users/kaikreuzer/orgs","repos_url":"https://api.github.com/users/kaikreuzer/repos","events_url":"https://api.github.com/users/kaikreuzer/events{/privacy}","received_events_url":"https://api.github.com/users/kaikreuzer/received_events","type":"User","site_admin":false},"repo":{"id":15803647,"name":"smarthome","full_name":"kaikreuzer/smarthome","owner":{"login":"kaikreuzer","id":3244965,"avatar_url":"https://avatars.githubusercontent.com/u/3244965?v=3","gravatar_id":"","url":"https://api.github.com/users/kaikreuzer","html_url":"https://github.com/kaikreuzer","followers_url":"https://api.github.com/users/kaikreuzer/followers","following_url":"https://api.github.com/users/kaikreuzer/following{/other_user}","gists_url":"https://api.github.com/users/kaikreuzer/gists{/gist_id}","starred_url":"https://api.github.com/users/kaikreuzer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaikreuzer/subscriptions","organizations_url":"https://api.github.com/users/kaikreuzer/orgs","repos_url":"https://api.github.com/users/kaikreuzer/repos","events_url":"https://api.github.com/users/kaikreuzer/events{/privacy}","received_events_url":"https://api.github.com/users/kaikreuzer/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kaikreuzer/smarthome","description":"Eclipse SmartHome project","fork":true,"url":"https://api.github.com/repos/kaikreuzer/smarthome","forks_url":"https://api.github.com/repos/kaikreuzer/smarthome/forks","keys_url":"https://api.github.com/repos/kaikreuzer/smarthome/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kaikreuzer/smarthome/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kaikreuzer/smarthome/teams","hooks_url":"https://api.github.com/repos/kaikreuzer/smarthome/hooks","issue_events_url":"https://api.github.com/repos/kaikreuzer/smarthome/issues/events{/number}","events_url":"https://api.github.com/repos/kaikreuzer/smarthome/events","assignees_url":"https://api.github.com/repos/kaikreuzer/smarthome/assignees{/user}","branches_url":"https://api.github.com/repos/kaikreuzer/smarthome/branches{/branch}","tags_url":"https://api.github.com/repos/kaikreuzer/smarthome/tags","blobs_url":"https://api.github.com/repos/kaikreuzer/smarthome/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kaikreuzer/smarthome/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kaikreuzer/smarthome/git/refs{/sha}","trees_url":"https://api.github.com/repos/kaikreuzer/smarthome/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kaikreuzer/smarthome/statuses/{sha}","languages_url":"https://api.github.com/repos/kaikreuzer/smarthome/languages","stargazers_url":"https://api.github.com/repos/kaikreuzer/smarthome/stargazers","contributors_url":"https://api.github.com/repos/kaikreuzer/smarthome/contributors","subscribers_url":"https://api.github.com/repos/kaikreuzer/smarthome/subscribers","subscription_url":"https://api.github.com/repos/kaikreuzer/smarthome/subscription","commits_url":"https://api.github.com/repos/kaikreuzer/smarthome/commits{/sha}","git_commits_url":"https://api.github.com/repos/kaikreuzer/smarthome/git/commits{/sha}","comments_url":"https://api.github.com/repos/kaikreuzer/smarthome/comments{/number}","issue_comment_url":"https://api.github.com/repos/kaikreuzer/smarthome/issues/comments{/number}","contents_url":"https://api.github.com/repos/kaikreuzer/smarthome/contents/{+path}","compare_url":"https://api.github.com/repos/kaikreuzer/smarthome/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kaikreuzer/smarthome/merges","archive_url":"https://api.github.com/repos/kaikreuzer/smarthome/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kaikreuzer/smarthome/downloads","issues_url":"https://api.github.com/repos/kaikreuzer/smarthome/issues{/number}","pulls_url":"https://api.github.com/repos/kaikreuzer/smarthome/pulls{/number}","milestones_url":"https://api.github.com/repos/kaikreuzer/smarthome/milestones{/number}","notifications_url":"https://api.github.com/repos/kaikreuzer/smarthome/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kaikreuzer/smarthome/labels{/name}","releases_url":"https://api.github.com/repos/kaikreuzer/smarthome/releases{/id}","created_at":"2014-01-10T16:46:33Z","updated_at":"2015-06-15T14:02:29Z","pushed_at":"2015-07-23T08:05:14Z","git_url":"git://github.com/kaikreuzer/smarthome.git","ssh_url":"git@github.com:kaikreuzer/smarthome.git","clone_url":"https://github.com/kaikreuzer/smarthome.git","svn_url":"https://github.com/kaikreuzer/smarthome","homepage":null,"size":10990,"stargazers_count":2,"watchers_count":2,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":2,"default_branch":"master"}},"base":{"label":"eclipse:master","ref":"master","sha":"fe87e7e4de922e3b453f667375e8406a314770fe","user":{"login":"eclipse","id":56974,"avatar_url":"https://avatars.githubusercontent.com/u/56974?v=3","gravatar_id":"","url":"https://api.github.com/users/eclipse","html_url":"https://github.com/eclipse","followers_url":"https://api.github.com/users/eclipse/followers","following_url":"https://api.github.com/users/eclipse/following{/other_user}","gists_url":"https://api.github.com/users/eclipse/gists{/gist_id}","starred_url":"https://api.github.com/users/eclipse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eclipse/subscriptions","organizations_url":"https://api.github.com/users/eclipse/orgs","repos_url":"https://api.github.com/users/eclipse/repos","events_url":"https://api.github.com/users/eclipse/events{/privacy}","received_events_url":"https://api.github.com/users/eclipse/received_events","type":"Organization","site_admin":false},"repo":{"id":15778981,"name":"smarthome","full_name":"eclipse/smarthome","owner":{"login":"eclipse","id":56974,"avatar_url":"https://avatars.githubusercontent.com/u/56974?v=3","gravatar_id":"","url":"https://api.github.com/users/eclipse","html_url":"https://github.com/eclipse","followers_url":"https://api.github.com/users/eclipse/followers","following_url":"https://api.github.com/users/eclipse/following{/other_user}","gists_url":"https://api.github.com/users/eclipse/gists{/gist_id}","starred_url":"https://api.github.com/users/eclipse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eclipse/subscriptions","organizations_url":"https://api.github.com/users/eclipse/orgs","repos_url":"https://api.github.com/users/eclipse/repos","events_url":"https://api.github.com/users/eclipse/events{/privacy}","received_events_url":"https://api.github.com/users/eclipse/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/eclipse/smarthome","description":"Eclipse SmartHome project","fork":false,"url":"https://api.github.com/repos/eclipse/smarthome","forks_url":"https://api.github.com/repos/eclipse/smarthome/forks","keys_url":"https://api.github.com/repos/eclipse/smarthome/keys{/key_id}","collaborators_url":"https://api.github.com/repos/eclipse/smarthome/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/eclipse/smarthome/teams","hooks_url":"https://api.github.com/repos/eclipse/smarthome/hooks","issue_events_url":"https://api.github.com/repos/eclipse/smarthome/issues/events{/number}","events_url":"https://api.github.com/repos/eclipse/smarthome/events","assignees_url":"https://api.github.com/repos/eclipse/smarthome/assignees{/user}","branches_url":"https://api.github.com/repos/eclipse/smarthome/branches{/branch}","tags_url":"https://api.github.com/repos/eclipse/smarthome/tags","blobs_url":"https://api.github.com/repos/eclipse/smarthome/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/eclipse/smarthome/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/eclipse/smarthome/git/refs{/sha}","trees_url":"https://api.github.com/repos/eclipse/smarthome/git/trees{/sha}","statuses_url":"https://api.github.com/repos/eclipse/smarthome/statuses/{sha}","languages_url":"https://api.github.com/repos/eclipse/smarthome/languages","stargazers_url":"https://api.github.com/repos/eclipse/smarthome/stargazers","contributors_url":"https://api.github.com/repos/eclipse/smarthome/contributors","subscribers_url":"https://api.github.com/repos/eclipse/smarthome/subscribers","subscription_url":"https://api.github.com/repos/eclipse/smarthome/subscription","commits_url":"https://api.github.com/repos/eclipse/smarthome/commits{/sha}","git_commits_url":"https://api.github.com/repos/eclipse/smarthome/git/commits{/sha}","comments_url":"https://api.github.com/repos/eclipse/smarthome/comments{/number}","issue_comment_url":"https://api.github.com/repos/eclipse/smarthome/issues/comments{/number}","contents_url":"https://api.github.com/repos/eclipse/smarthome/contents/{+path}","compare_url":"https://api.github.com/repos/eclipse/smarthome/compare/{base}...{head}","merges_url":"https://api.github.com/repos/eclipse/smarthome/merges","archive_url":"https://api.github.com/repos/eclipse/smarthome/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/eclipse/smarthome/downloads","issues_url":"https://api.github.com/repos/eclipse/smarthome/issues{/number}","pulls_url":"https://api.github.com/repos/eclipse/smarthome/pulls{/number}","milestones_url":"https://api.github.com/repos/eclipse/smarthome/milestones{/number}","notifications_url":"https://api.github.com/repos/eclipse/smarthome/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/eclipse/smarthome/labels{/name}","releases_url":"https://api.github.com/repos/eclipse/smarthome/releases{/id}","created_at":"2014-01-09T20:47:19Z","updated_at":"2015-07-22T01:00:27Z","pushed_at":"2015-07-23T08:05:14Z","git_url":"git://github.com/eclipse/smarthome.git","ssh_url":"git@github.com:eclipse/smarthome.git","clone_url":"https://github.com/eclipse/smarthome.git","svn_url":"https://github.com/eclipse/smarthome","homepage":null,"size":16996,"stargazers_count":167,"watchers_count":167,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":157,"mirror_url":null,"open_issues_count":10,"forks":157,"open_issues":10,"watchers":167,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/eclipse/smarthome/pulls/387"},"html":{"href":"https://github.com/eclipse/smarthome/pull/387"},"issue":{"href":"https://api.github.com/repos/eclipse/smarthome/issues/387"},"comments":{"href":"https://api.github.com/repos/eclipse/smarthome/issues/387/comments"},"review_comments":{"href":"https://api.github.com/repos/eclipse/smarthome/pulls/387/comments"},"review_comment":{"href":"https://api.github.com/repos/eclipse/smarthome/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/eclipse/smarthome/pulls/387/commits"},"statuses":{"href":"https://api.github.com/repos/eclipse/smarthome/statuses/0d7a5c062a1765fe0ce3b1303e4da9fc09446f34"}}}}