Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  Upgrade bazlets to latest stable-2.16 to build with 2.16.22 API

Change-Id: If9d1980101df3173a6b556f2b7f440f5efd7bdcf
diff --git a/.bazelversion b/.bazelversion
index fd2a018..47b322c 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.1.0
+3.4.1
diff --git a/BUILD b/BUILD
index ca6a1cd..ab1c177 100644
--- a/BUILD
+++ b/BUILD
@@ -6,6 +6,7 @@
     manifest_entries = [
         "Gerrit-PluginName: messageoftheday",
         "Gerrit-Module: com.googlesource.gerrit.plugins.messageoftheday.Module",
+        "Gerrit-HttpModule: com.googlesource.gerrit.plugins.messageoftheday.HttpModule",
         "Implementation-Title: Plugin messageoftheday",
         "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/messageoftheday",
     ],
diff --git a/WORKSPACE b/WORKSPACE
index d2dafa0..a0c4bc3 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,24 +3,13 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "8ad33887665f4f6adf7cb465a03f6bd81b95e01d",
+    commit = "7a9ae377b519934c87184cc05845663ed708b69c",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
-# Snapshot Plugin API
-#load(
-#    "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl",
-#    "gerrit_api_maven_local",
-#)
-
-# Load snapshot Plugin API
-#gerrit_api_maven_local()
-
-# Release Plugin API
 load(
     "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
     "gerrit_api",
 )
 
-# Load release Plugin API
 gerrit_api()
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java
new file mode 100644
index 0000000..c0181a5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java
@@ -0,0 +1,120 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.messageoftheday;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GetMessage implements RestReadView<ConfigResource> {
+  private static final String SECTION_MESSAGE = "message";
+  private static final String KEY_ID = "id";
+  private static final String KEY_STARTS_AT = "startsAt";
+  private static final String KEY_EXPIRES_AT = "expiresAt";
+
+  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd:HHmm");
+
+  private static final Logger log = LoggerFactory.getLogger(GetMessage.class);
+
+  private final File cfgFile;
+  private final Path dataDirPath;
+
+  private volatile FileBasedConfig cfg;
+
+  @Inject
+  public GetMessage(
+      @PluginName String pluginName, @PluginData Path dataDirPath, SitePaths sitePaths) {
+    this.dataDirPath = dataDirPath;
+    this.cfgFile = sitePaths.etc_dir.resolve(pluginName + ".config").toFile();
+  }
+
+  @Override
+  public MessageOfTheDayInfo apply(ConfigResource rsrc) {
+    MessageOfTheDayInfo motd = new MessageOfTheDayInfo();
+    cfg = new FileBasedConfig(cfgFile, FS.DETECTED);
+    try {
+      cfg.load();
+    } catch (ConfigInvalidException | IOException e) {
+      return null;
+    }
+
+    motd.id = cfg.getString(SECTION_MESSAGE, null, KEY_ID);
+    if (Strings.isNullOrEmpty(motd.id)) {
+      log.warn("id not defined, no message will be shown");
+      return null;
+    }
+
+    try {
+      motd.expiresAt = DATE_FORMAT.parse(cfg.getString(SECTION_MESSAGE, null, KEY_EXPIRES_AT));
+    } catch (ParseException | NullPointerException e) {
+      log.warn("expiresAt not defined, no message will be shown");
+      return null;
+    }
+
+    try {
+      motd.html = new String(Files.readAllBytes(dataDirPath.resolve(motd.id + ".html")), UTF_8);
+    } catch (IOException e1) {
+      log.warn(
+          String.format(
+              "No HTML-file was found for message %s, no message will be shown", motd.id));
+      return null;
+    }
+
+    try {
+      String startsAt = cfg.getString(SECTION_MESSAGE, null, KEY_STARTS_AT);
+      motd.startsAt = Strings.isNullOrEmpty(startsAt) ? new Date() : DATE_FORMAT.parse(startsAt);
+    } catch (ParseException e) {
+      motd.startsAt = new Date();
+    }
+
+    if (motd.startsAt.compareTo(new Date()) > 0 || motd.expiresAt.compareTo(new Date()) < 0) {
+      return null;
+    }
+
+    motd.redisplay = getRedisplay();
+
+    return motd;
+  }
+
+  private Date getRedisplay() {
+    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+    cal.set(Calendar.HOUR_OF_DAY, 0);
+    cal.set(Calendar.MINUTE, 0);
+    cal.set(Calendar.SECOND, 0);
+    cal.set(Calendar.MILLISECOND, 0);
+    cal.add(Calendar.DAY_OF_MONTH, 1);
+    return cal.getTime();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/HttpModule.java
new file mode 100644
index 0000000..d10983a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/HttpModule.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.messageoftheday;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.webui.JavaScriptPlugin;
+import com.google.gerrit.extensions.webui.WebUiPlugin;
+import com.google.inject.AbstractModule;
+
+public class HttpModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    DynamicSet.bind(binder(), WebUiPlugin.class)
+        .toInstance(new JavaScriptPlugin("gr-messageoftheday.html"));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageOfTheDayImpl.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageOfTheDayImpl.java
deleted file mode 100644
index 83adccc..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageOfTheDayImpl.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.googlesource.gerrit.plugins.messageoftheday;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Strings;
-import com.google.gerrit.extensions.annotations.PluginData;
-import com.google.gerrit.extensions.annotations.PluginName;
-import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-class MessageOfTheDayImpl extends MessageOfTheDay {
-  private static final Logger log = LoggerFactory.getLogger(MessageOfTheDayImpl.class);
-
-  private final File data;
-  private final String id;
-  private final String startsAt;
-  private final String expiresAt;
-  private final String msg;
-
-  @Inject
-  public MessageOfTheDayImpl(
-      PluginConfigFactory configFactory, @PluginName String myName, @PluginData File data) {
-    this.data = data;
-    Config cfg = configFactory.getGlobalPluginConfig(myName);
-    id = cfg.getString("message", null, "id");
-    String configuredStartsAt = Strings.emptyToNull(cfg.getString("message", null, "startsAt"));
-    startsAt = MoreObjects.firstNonNull(configuredStartsAt, now());
-    expiresAt = cfg.getString("message", null, "expiresAt");
-    msg = message();
-  }
-
-  @Override
-  public String getHtmlMessage() {
-    if (Strings.isNullOrEmpty(id)) {
-      return null;
-    }
-
-    if (Strings.isNullOrEmpty(expiresAt)) {
-      log.warn("expiresAt not defined, no message will be shown");
-      return null;
-    }
-
-    if (now().compareTo(startsAt) < 0 || now().compareTo(expiresAt) > 0) {
-      return null;
-    }
-
-    return msg;
-  }
-
-  @Override
-  public String getMessageId() {
-    return id;
-  }
-
-  private String message() {
-    if (Strings.isNullOrEmpty(id)) {
-      return null;
-    }
-
-    Path p = new File(data, id + ".html").toPath();
-    try {
-      return new String(Files.readAllBytes(p), UTF_8);
-    } catch (IOException e) {
-      log.warn("Couldn't read content of the mesage with id = " + id, e);
-      return null;
-    }
-  }
-
-  private static String now() {
-    return new SimpleDateFormat("yyyyMMdd:HHmm").format(new Date());
-  }
-}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageOfTheDayInfo.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageOfTheDayInfo.java
new file mode 100644
index 0000000..eed52ce
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageOfTheDayInfo.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.messageoftheday;
+
+import java.util.Date;
+
+/** REST API representation of a "message of the day". */
+public class MessageOfTheDayInfo {
+  /** The ID of the message. */
+  public String id;
+  /** The time from which on the message will be displayed. */
+  public Date startsAt;
+  /** The time from which on the message will not be displayed anymore. */
+  public Date expiresAt;
+  /** The date and time the message will be displayed again after being dismissed by the user. */
+  public Date redisplay;
+  /** The message in HTML-format. */
+  public String html;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/Module.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/Module.java
index c9ea760..1164004 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/Module.java
@@ -14,13 +14,20 @@
 
 package com.googlesource.gerrit.plugins.messageoftheday;
 
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.systemstatus.MessageOfTheDay;
+import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+
+import com.google.gerrit.extensions.restapi.RestApiModule;
 import com.google.inject.AbstractModule;
 
 class Module extends AbstractModule {
   @Override
   protected void configure() {
-    DynamicSet.bind(binder(), MessageOfTheDay.class).to(MessageOfTheDayImpl.class);
+    install(
+        new RestApiModule() {
+          @Override
+          protected void configure() {
+            get(CONFIG_KIND, "message").to(GetMessage.class);
+          }
+        });
   }
 }
diff --git a/src/main/resources/Documentation/rest-api-config.md b/src/main/resources/Documentation/rest-api-config.md
new file mode 100644
index 0000000..86d923c
--- /dev/null
+++ b/src/main/resources/Documentation/rest-api-config.md
@@ -0,0 +1,57 @@
+@PLUGIN@ - /config/ REST API
+============================
+
+This page describes the REST endpoints that are added by the @PLUGIN@
+plugin.
+
+Please also take note of the general information on the
+[REST API](../../../Documentation/rest-api.html).
+
+<a id="config-endpoints"> Config Endpoints
+------------------------------------------
+
+### <a id="get-menus"> Get Menus
+_GET /config/server/@PLUGIN@~message/_
+
+Gets the message of the day.
+
+#### Request
+
+```
+  GET /config/server/@PLUGIN@~message/ HTTP/1.0
+```
+
+As response a [MessageOfTheDayInfo](./rest-api-config.md#MessageOfTheDayInfo) entity
+is returned that contains the message and associated metadata.
+
+#### Response
+
+```
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "id": "hello",
+    "starts_at": "Feb 4, 2020 5:53:00 PM",
+    "expires_at": "Dec 30, 2020 6:00:00 PM",
+    "redisplay": "Feb 5, 2020 1:00:00 AM",
+    "html": "hello you!"
+  }
+```
+
+### MessageOfTheDayInfo
+
+The `MessageOfTheDayInfo` entity contains information about the message of the day.
+
+* `id`: ID of the message.
+* `starts_at`: Date, when the message will be first displayed
+* `expires_at`: Date, after which the message will not be displayed anymore
+* `redisplay`: Date, after which will the message be displayed again after dismissal
+* `html`: String, containing the HTML-formatted message
+
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
diff --git a/src/main/resources/static/gr-messageoftheday-banner.html b/src/main/resources/static/gr-messageoftheday-banner.html
new file mode 100644
index 0000000..102645d
--- /dev/null
+++ b/src/main/resources/static/gr-messageoftheday-banner.html
@@ -0,0 +1,38 @@
+<!--
+@license
+Copyright (C) 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<dom-module id="gr-messageoftheday-banner">
+  <template>
+    <style include="shared-styles">
+      #container {
+        background-color: lightyellow;
+        display: flex;
+        height: fit-content;
+        justify-content: space-between;
+        padding: 1em;
+      }
+    </style>
+    <div id="container" hidden$="[[_hidden]]">
+      <div id="message"></div>
+      <gr-button id="dismissMessageBtn"
+        link
+        on-tap="_handleDismissMessage">Dismiss</gr-button>
+    </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-messageoftheday-banner.js"></script>
+</dom-module>
diff --git a/src/main/resources/static/gr-messageoftheday-banner.js b/src/main/resources/static/gr-messageoftheday-banner.js
new file mode 100644
index 0000000..536c05d
--- /dev/null
+++ b/src/main/resources/static/gr-messageoftheday-banner.js
@@ -0,0 +1,54 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-messageoftheday-banner',
+
+    properties: {
+      _message: Object,
+      _hidden: {
+        type: Boolean,
+        value: true,
+      },
+    },
+
+    attached() {
+      this.plugin.restApi()
+        .get(`/config/server/${this.plugin.getPluginName()}~message`)
+        .then(message => {
+          if (!message || !message.html) {
+            return;
+          }
+          this._message = message;
+          this._isHidden();
+          this.$.message.innerHTML = this._message.html;
+        });
+    },
+
+    _handleDismissMessage() {
+      document.cookie =
+        `msg-${this._message.id}=1; expires=${this._message.redisplay}`;
+      this._hidden = true;
+    },
+
+    _isHidden() {
+      this._hidden = window.util.getCookie(`msg-${this._message.id}`) === '1';
+    },
+  });
+})();
diff --git a/src/main/resources/static/gr-messageoftheday.html b/src/main/resources/static/gr-messageoftheday.html
new file mode 100644
index 0000000..8758502
--- /dev/null
+++ b/src/main/resources/static/gr-messageoftheday.html
@@ -0,0 +1,27 @@
+<!--
+@license
+Copyright (C) 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import"
+      href="./gr-messageoftheday-banner.html">
+
+<dom-module id="gr-messageoftheday">
+  <script>
+    Gerrit.install(plugin => {
+      plugin.registerCustomComponent("banner", "gr-messageoftheday-banner");
+    });
+  </script>
+</dom-module>