{"action":"created","comment":{"url":"https://api.github.com/repos/apache/storm/pulls/comments/43894843","id":43894843,"diff_hunk":"@@ -0,0 +1,200 @@\n+/**\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * \"License\"); you may not use this file except in compliance\n+ * with the License.  You may obtain a copy of the License at\n+ * <p/>\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ * <p/>\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS,\n+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+ * See the License for the specific language governing permissions and\n+ * limitations under the License.\n+ */\n+package backtype.storm.topology;\n+\n+import backtype.storm.Config;\n+import backtype.storm.task.IOutputCollector;\n+import backtype.storm.task.OutputCollector;\n+import backtype.storm.task.TopologyContext;\n+import backtype.storm.tuple.Tuple;\n+import backtype.storm.windowing.TupleWindowImpl;\n+import backtype.storm.windowing.WindowLifecycleListener;\n+import backtype.storm.windowing.WindowManager;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import java.util.List;\n+import java.util.Map;\n+import java.util.concurrent.TimeUnit;\n+\n+import static backtype.storm.topology.base.BaseWindowedBolt.Count;\n+import static backtype.storm.topology.base.BaseWindowedBolt.Duration;\n+\n+/**\n+ * An {@link IWindowedBolt} wrapper that does the windowing of tuples.\n+ */\n+public class WindowedBoltExecutor implements IRichBolt {\n+    private static final Logger LOG = LoggerFactory.getLogger(WindowedBoltExecutor.class);\n+\n+    private IWindowedBolt bolt;\n+    private transient WindowedOutputCollector windowedOutputCollector;\n+    private transient WindowLifecycleListener<Tuple> listener;\n+    private transient WindowManager<Tuple> windowManager;\n+\n+    public WindowedBoltExecutor(IWindowedBolt bolt) {\n+        this.bolt = bolt;\n+    }\n+\n+    private int getTopologyTimeoutMillis(Map stormConf) {\n+        if (stormConf.containsKey(Config.TOPOLOGY_ENABLE_MESSAGE_TIMEOUTS)) {\n+            boolean timeOutsEnabled = (boolean) stormConf.get(Config.TOPOLOGY_ENABLE_MESSAGE_TIMEOUTS);\n+            if (!timeOutsEnabled) {\n+                return Integer.MAX_VALUE;\n+            }\n+        }\n+        int timeout = 0;\n+        if (stormConf.containsKey(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS)) {\n+            timeout = ((Number) stormConf.get(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS)).intValue();\n+        }\n+        return timeout * 1000;\n+    }\n+\n+    private void ensureDurationLessThanTimeout(int duration, int timeout) {\n+        if (duration > timeout) {\n+            throw new IllegalArgumentException(\"Window duration (length + sliding interval) value \" + duration +\n+                                                       \" is more than \" + Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS +\n+                                                       \" value \" + timeout);\n+        }\n+    }\n+\n+    // TODO: add more validation\n+    private void validate(Map stormConf, Count windowLengthCount, Duration windowLengthDuration,\n+                          Count slidingIntervalCount, Duration slidingIntervalDuration) {\n+\n+        int topologyTimeout = getTopologyTimeoutMillis(stormConf);\n+        if (windowLengthDuration != null && slidingIntervalDuration != null) {\n+            ensureDurationLessThanTimeout(windowLengthDuration.value + slidingIntervalDuration.value, topologyTimeout);\n+        } else if (windowLengthDuration != null) {\n+            ensureDurationLessThanTimeout(windowLengthDuration.value, topologyTimeout);\n+        }\n+    }\n+\n+    private WindowManager<Tuple> initWindowManager(WindowLifecycleListener<Tuple> lifecycleListener, Map stormConf) {\n+        WindowManager<Tuple> manager = new WindowManager<>(lifecycleListener);\n+        Duration windowLengthDuration = null;\n+        Count windowLengthCount = null;\n+        Duration slidingIntervalDuration = null;\n+        Count slidingIntervalCount = null;\n+        if (stormConf.containsKey(Config.TOPOLOGY_BOLTS_WINDOW_LENGTH_COUNT)) {\n+            windowLengthCount = new Count(((Number) stormConf.get(Config.TOPOLOGY_BOLTS_WINDOW_LENGTH_COUNT)).intValue());\n+        } else if (stormConf.containsKey(Config.TOPOLOGY_BOLTS_WINDOW_LENGTH_DURATION_MS)) {\n+            windowLengthDuration = new Duration(\n+                    ((Number) stormConf.get(Config.TOPOLOGY_BOLTS_WINDOW_LENGTH_DURATION_MS)).intValue(),\n+                    TimeUnit.MILLISECONDS);\n+        }\n+\n+        if (stormConf.containsKey(Config.TOPOLOGY_BOLTS_SLIDING_INTERVAL_COUNT)) {\n+            slidingIntervalCount = new Count(((Number) stormConf.get(Config.TOPOLOGY_BOLTS_SLIDING_INTERVAL_COUNT)).intValue());\n+        } else if (stormConf.containsKey(Config.TOPOLOGY_BOLTS_SLIDING_INTERVAL_DURATION_MS)) {\n+            slidingIntervalDuration = new Duration(((Number) stormConf.get(Config.TOPOLOGY_BOLTS_SLIDING_INTERVAL_DURATION_MS)).intValue(), TimeUnit.MILLISECONDS);\n+        } else {\n+            // default is a sliding window of count 1\n+            slidingIntervalCount = new Count(1);\n+        }\n+        // validate\n+        validate(stormConf, windowLengthCount, windowLengthDuration,\n+                 slidingIntervalCount, slidingIntervalDuration);\n+        if (windowLengthCount != null) {\n+            manager.setWindowLength(windowLengthCount);\n+        } else {\n+            manager.setWindowLength(windowLengthDuration);\n+        }\n+        if (slidingIntervalCount != null) {\n+            manager.setSlidingInterval(slidingIntervalCount);\n+        } else {\n+            manager.setSlidingInterval(slidingIntervalDuration);\n+        }\n+        return manager;\n+    }\n+\n+    @Override\n+    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {\n+        this.windowedOutputCollector = new WindowedOutputCollector(collector);\n+        bolt.prepare(stormConf, context, windowedOutputCollector);\n+        this.listener = newWindowLifecycleListener();\n+        this.windowManager = initWindowManager(listener, stormConf);\n+        LOG.info(\"Initialized window manager {} \", this.windowManager);","path":"storm-core/src/jvm/backtype/storm/topology/WindowedBoltExecutor.java","position":131,"original_position":131,"commit_id":"77429d07d7c080962b8c414cf02ee93839b25ca2","original_commit_id":"77429d07d7c080962b8c414cf02ee93839b25ca2","user":{"login":"revans2","id":3441321,"avatar_url":"https://avatars.githubusercontent.com/u/3441321?v=3","gravatar_id":"","url":"https://api.github.com/users/revans2","html_url":"https://github.com/revans2","followers_url":"https://api.github.com/users/revans2/followers","following_url":"https://api.github.com/users/revans2/following{/other_user}","gists_url":"https://api.github.com/users/revans2/gists{/gist_id}","starred_url":"https://api.github.com/users/revans2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/revans2/subscriptions","organizations_url":"https://api.github.com/users/revans2/orgs","repos_url":"https://api.github.com/users/revans2/repos","events_url":"https://api.github.com/users/revans2/events{/privacy}","received_events_url":"https://api.github.com/users/revans2/received_events","type":"User","site_admin":false},"body":"This looks more like a debug statement to me.","created_at":"2015-11-04T15:36:51Z","updated_at":"2015-11-04T15:36:51Z","html_url":"https://github.com/apache/storm/pull/855#discussion_r43894843","pull_request_url":"https://api.github.com/repos/apache/storm/pulls/855","_links":{"self":{"href":"https://api.github.com/repos/apache/storm/pulls/comments/43894843"},"html":{"href":"https://github.com/apache/storm/pull/855#discussion_r43894843"},"pull_request":{"href":"https://api.github.com/repos/apache/storm/pulls/855"}}},"pull_request":{"url":"https://api.github.com/repos/apache/storm/pulls/855","id":49678428,"html_url":"https://github.com/apache/storm/pull/855","diff_url":"https://github.com/apache/storm/pull/855.diff","patch_url":"https://github.com/apache/storm/pull/855.patch","issue_url":"https://api.github.com/repos/apache/storm/issues/855","number":855,"state":"open","locked":false,"title":"STORM-1167: Add windowing support for storm core","user":{"login":"arunmahadevan","id":6792890,"avatar_url":"https://avatars.githubusercontent.com/u/6792890?v=3","gravatar_id":"","url":"https://api.github.com/users/arunmahadevan","html_url":"https://github.com/arunmahadevan","followers_url":"https://api.github.com/users/arunmahadevan/followers","following_url":"https://api.github.com/users/arunmahadevan/following{/other_user}","gists_url":"https://api.github.com/users/arunmahadevan/gists{/gist_id}","starred_url":"https://api.github.com/users/arunmahadevan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arunmahadevan/subscriptions","organizations_url":"https://api.github.com/users/arunmahadevan/orgs","repos_url":"https://api.github.com/users/arunmahadevan/repos","events_url":"https://api.github.com/users/arunmahadevan/events{/privacy}","received_events_url":"https://api.github.com/users/arunmahadevan/received_events","type":"User","site_admin":false},"body":"Currently, topologies that needs windowing support requires writing custom logic inside bolts making it tedious to handle the windowing and acking logic with custom logic.\r\n\r\nThe PR proposes to add framework level support to core storm bolts to process tuples in a time or a count based window. Sliding and tumbling windows based on tuple count or time duration are supported.\r\n\r\nA new bolt interface is added for bolts that needs windowing support.\r\n\r\n```\r\npublic interface IWindowedBolt extends IComponent {\r\n    void prepare(Map stormConf, TopologyContext context, OutputCollector collector);\r\n    /**\r\n     * Process tuples falling within the window and optionally emit \r\n     * new tuples based on the tuples in the input window.\r\n     */\r\n    void execute(TupleWindow inputWindow);\r\n    void cleanup();\r\n}\r\n```\r\n\r\n`TupleWindow` gives access to the current tuples in the window, the tuples that expired and the new tuples that are added since last window was computed which will be useful for efficient windowing computations.\r\n\r\nThe `TopologyBuilder` wraps the IWindowedBolt implementation in an internal bolt `WindowedBoltExecutor` (similar to `BasicBoltExecutor`), which receives the tuples and hands it off to IWindowedBolt when the window conditions are met. The tuples are automatically acked when they fall out of the window and the tuples in a window are automatically anchored with the tuple emitted from an IWindowedBolt to provide atleast once guarentee. An example topology `SlidingWindowTopology` shows how to use the apis to compute a sliding window sum.\r\n\r\nThe tuples are tracked with the system timestamp when they are received by the bolt. Later we can add support for tuple based timestamps (e.g the ts when the tuple was generated). There is also scope for minimizing duplicates by tracking the evaluated tuples (based on some id) in a persistent store.\r\n\r\nSimiliar windowing constructs needs to be added to storm trident apis as well. That can be addressed once the basic support to store core is added and the common logic can be reused.\r\n","created_at":"2015-11-04T11:42:54Z","updated_at":"2015-11-04T15:36:51Z","closed_at":null,"merged_at":null,"merge_commit_sha":"2a285417a9dafbae93defcdf76adc30f677465fd","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/apache/storm/pulls/855/commits","review_comments_url":"https://api.github.com/repos/apache/storm/pulls/855/comments","review_comment_url":"https://api.github.com/repos/apache/storm/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/storm/issues/855/comments","statuses_url":"https://api.github.com/repos/apache/storm/statuses/77429d07d7c080962b8c414cf02ee93839b25ca2","head":{"label":"arunmahadevan:windowing","ref":"windowing","sha":"77429d07d7c080962b8c414cf02ee93839b25ca2","user":{"login":"arunmahadevan","id":6792890,"avatar_url":"https://avatars.githubusercontent.com/u/6792890?v=3","gravatar_id":"","url":"https://api.github.com/users/arunmahadevan","html_url":"https://github.com/arunmahadevan","followers_url":"https://api.github.com/users/arunmahadevan/followers","following_url":"https://api.github.com/users/arunmahadevan/following{/other_user}","gists_url":"https://api.github.com/users/arunmahadevan/gists{/gist_id}","starred_url":"https://api.github.com/users/arunmahadevan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arunmahadevan/subscriptions","organizations_url":"https://api.github.com/users/arunmahadevan/orgs","repos_url":"https://api.github.com/users/arunmahadevan/repos","events_url":"https://api.github.com/users/arunmahadevan/events{/privacy}","received_events_url":"https://api.github.com/users/arunmahadevan/received_events","type":"User","site_admin":false},"repo":{"id":38767161,"name":"storm","full_name":"arunmahadevan/storm","owner":{"login":"arunmahadevan","id":6792890,"avatar_url":"https://avatars.githubusercontent.com/u/6792890?v=3","gravatar_id":"","url":"https://api.github.com/users/arunmahadevan","html_url":"https://github.com/arunmahadevan","followers_url":"https://api.github.com/users/arunmahadevan/followers","following_url":"https://api.github.com/users/arunmahadevan/following{/other_user}","gists_url":"https://api.github.com/users/arunmahadevan/gists{/gist_id}","starred_url":"https://api.github.com/users/arunmahadevan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arunmahadevan/subscriptions","organizations_url":"https://api.github.com/users/arunmahadevan/orgs","repos_url":"https://api.github.com/users/arunmahadevan/repos","events_url":"https://api.github.com/users/arunmahadevan/events{/privacy}","received_events_url":"https://api.github.com/users/arunmahadevan/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/arunmahadevan/storm","description":"Mirror of Apache Storm","fork":true,"url":"https://api.github.com/repos/arunmahadevan/storm","forks_url":"https://api.github.com/repos/arunmahadevan/storm/forks","keys_url":"https://api.github.com/repos/arunmahadevan/storm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/arunmahadevan/storm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/arunmahadevan/storm/teams","hooks_url":"https://api.github.com/repos/arunmahadevan/storm/hooks","issue_events_url":"https://api.github.com/repos/arunmahadevan/storm/issues/events{/number}","events_url":"https://api.github.com/repos/arunmahadevan/storm/events","assignees_url":"https://api.github.com/repos/arunmahadevan/storm/assignees{/user}","branches_url":"https://api.github.com/repos/arunmahadevan/storm/branches{/branch}","tags_url":"https://api.github.com/repos/arunmahadevan/storm/tags","blobs_url":"https://api.github.com/repos/arunmahadevan/storm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/arunmahadevan/storm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/arunmahadevan/storm/git/refs{/sha}","trees_url":"https://api.github.com/repos/arunmahadevan/storm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/arunmahadevan/storm/statuses/{sha}","languages_url":"https://api.github.com/repos/arunmahadevan/storm/languages","stargazers_url":"https://api.github.com/repos/arunmahadevan/storm/stargazers","contributors_url":"https://api.github.com/repos/arunmahadevan/storm/contributors","subscribers_url":"https://api.github.com/repos/arunmahadevan/storm/subscribers","subscription_url":"https://api.github.com/repos/arunmahadevan/storm/subscription","commits_url":"https://api.github.com/repos/arunmahadevan/storm/commits{/sha}","git_commits_url":"https://api.github.com/repos/arunmahadevan/storm/git/commits{/sha}","comments_url":"https://api.github.com/repos/arunmahadevan/storm/comments{/number}","issue_comment_url":"https://api.github.com/repos/arunmahadevan/storm/issues/comments{/number}","contents_url":"https://api.github.com/repos/arunmahadevan/storm/contents/{+path}","compare_url":"https://api.github.com/repos/arunmahadevan/storm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/arunmahadevan/storm/merges","archive_url":"https://api.github.com/repos/arunmahadevan/storm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/arunmahadevan/storm/downloads","issues_url":"https://api.github.com/repos/arunmahadevan/storm/issues{/number}","pulls_url":"https://api.github.com/repos/arunmahadevan/storm/pulls{/number}","milestones_url":"https://api.github.com/repos/arunmahadevan/storm/milestones{/number}","notifications_url":"https://api.github.com/repos/arunmahadevan/storm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/arunmahadevan/storm/labels{/name}","releases_url":"https://api.github.com/repos/arunmahadevan/storm/releases{/id}","created_at":"2015-07-08T16:48:04Z","updated_at":"2015-07-08T16:48:07Z","pushed_at":"2015-11-04T11:39:44Z","git_url":"git://github.com/arunmahadevan/storm.git","ssh_url":"git@github.com:arunmahadevan/storm.git","clone_url":"https://github.com/arunmahadevan/storm.git","svn_url":"https://github.com/arunmahadevan/storm","homepage":null,"size":22637,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"apache:master","ref":"master","sha":"f3568d73c8832cdf2a6a6ec06b929c8b7bb96c10","user":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":14135470,"name":"storm","full_name":"apache/storm","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/storm","description":"Mirror of Apache Storm","fork":false,"url":"https://api.github.com/repos/apache/storm","forks_url":"https://api.github.com/repos/apache/storm/forks","keys_url":"https://api.github.com/repos/apache/storm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/storm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/storm/teams","hooks_url":"https://api.github.com/repos/apache/storm/hooks","issue_events_url":"https://api.github.com/repos/apache/storm/issues/events{/number}","events_url":"https://api.github.com/repos/apache/storm/events","assignees_url":"https://api.github.com/repos/apache/storm/assignees{/user}","branches_url":"https://api.github.com/repos/apache/storm/branches{/branch}","tags_url":"https://api.github.com/repos/apache/storm/tags","blobs_url":"https://api.github.com/repos/apache/storm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/storm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/storm/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/storm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/storm/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/storm/languages","stargazers_url":"https://api.github.com/repos/apache/storm/stargazers","contributors_url":"https://api.github.com/repos/apache/storm/contributors","subscribers_url":"https://api.github.com/repos/apache/storm/subscribers","subscription_url":"https://api.github.com/repos/apache/storm/subscription","commits_url":"https://api.github.com/repos/apache/storm/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/storm/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/storm/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/storm/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/storm/contents/{+path}","compare_url":"https://api.github.com/repos/apache/storm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/storm/merges","archive_url":"https://api.github.com/repos/apache/storm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/storm/downloads","issues_url":"https://api.github.com/repos/apache/storm/issues{/number}","pulls_url":"https://api.github.com/repos/apache/storm/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/storm/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/storm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/storm/labels{/name}","releases_url":"https://api.github.com/repos/apache/storm/releases{/id}","created_at":"2013-11-05T08:00:14Z","updated_at":"2015-11-04T12:49:43Z","pushed_at":"2015-11-04T11:42:54Z","git_url":"git://github.com/apache/storm.git","ssh_url":"git@github.com:apache/storm.git","clone_url":"https://github.com/apache/storm.git","svn_url":"https://github.com/apache/storm","homepage":null,"size":827428,"stargazers_count":2497,"watchers_count":2497,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":1838,"mirror_url":"git://git.apache.org/incubator-storm.git","open_issues_count":96,"forks":1838,"open_issues":96,"watchers":2497,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/storm/pulls/855"},"html":{"href":"https://github.com/apache/storm/pull/855"},"issue":{"href":"https://api.github.com/repos/apache/storm/issues/855"},"comments":{"href":"https://api.github.com/repos/apache/storm/issues/855/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/storm/pulls/855/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/storm/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/storm/pulls/855/commits"},"statuses":{"href":"https://api.github.com/repos/apache/storm/statuses/77429d07d7c080962b8c414cf02ee93839b25ca2"}}}}