Implement the MessageOfTheDay functionality completely in the plugin

The MessageOfTheDay extension point was removed from Gerrit core. Thus,
this plugin was not working anymore with Gerrit version 3.0 or higher.

This change moves the complete handling of the messages to the plugin.
The messages are still configured in the
$GERRIT_SITE/etc/messageoftheday.config-file and are given in html
files in the $GERRIT_SITE/data/messageoftheday directory. The latest
configured message will be provided by a REST API endpoint implemented
by the plugin. A UI element using the 'banner' endpoint will show the
message as provided by the REST API.

Change-Id: I05f7e7c672389f809ee6ea143656d5335dd8ffe0
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/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>