Introduce MessageStore and move logic to FileBasedMessageStore This change is a preparation for introducing different message storage types. Change-Id: I7d5809a508f85e217320d4644b8ffde85bee58ee
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/ConfiguredMessage.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/ConfiguredMessage.java new file mode 100644 index 0000000..ae3901f --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/ConfiguredMessage.java
@@ -0,0 +1,29 @@ +// Copyright (C) 2025 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.auto.value.AutoValue; +import org.eclipse.jgit.lib.Config; + +@AutoValue +public abstract class ConfiguredMessage { + public static ConfiguredMessage create(Config cfg, String message) { + return new AutoValue_ConfiguredMessage(cfg, message); + } + + public abstract Config config(); + + public abstract String message(); +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/FileBasedMessageStore.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/FileBasedMessageStore.java new file mode 100644 index 0000000..4324086 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/FileBasedMessageStore.java
@@ -0,0 +1,101 @@ +// Copyright (C) 2025 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 com.google.common.io.Files.asCharSink; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.base.Strings; +import com.google.common.flogger.FluentLogger; +import com.google.common.io.CharSink; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; + +@Singleton +public class FileBasedMessageStore implements MessageStore { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final String SECTION_MESSAGE = "message"; + private static final String KEY_ID = "id"; + + private final File cfgFile; + private final Path dataDir; + + @Inject + FileBasedMessageStore(@ConfigFile File cfgFile, @DataDir Path dataDir) { + this.cfgFile = cfgFile; + this.dataDir = dataDir; + } + + @Override + public ConfiguredMessage getConfiguredMessage() throws MessageStoreException { + FileBasedConfig cfg = loadConfig(); + + String htmlFileId = cfg.getString(SECTION_MESSAGE, null, KEY_ID); + if (Strings.isNullOrEmpty(htmlFileId)) { + logger.atWarning().log("id not defined, no message will be shown"); + return ConfiguredMessage.create(cfg, null); + } + + String message = loadMessage(htmlFileId); + return ConfiguredMessage.create(cfg, message); + } + + private FileBasedConfig loadConfig() throws MessageStoreException { + FileBasedConfig cfg = new FileBasedConfig(cfgFile, FS.DETECTED); + try { + cfg.load(); + } catch (ConfigInvalidException | IOException e) { + throw new MessageStoreException("plugin cfg is invalid or could not be loaded", e); + } + return cfg; + } + + private String loadMessage(String id) { + try { + return new String(Files.readAllBytes(dataDir.resolve(id + ".html")), UTF_8); + } catch (IOException e1) { + logger.atWarning().log("No HTML-file was found for message %s, no message will be shown", id); + return null; + } + } + + @Override + public void saveConfiguredMessage(ConfiguredMessage message) throws MessageStoreException { + FileBasedConfig configFile = new FileBasedConfig(message.config(), cfgFile, FS.DETECTED); + + String id = configFile.getString(SECTION_MESSAGE, null, KEY_ID); + try { + Path path = dataDir.resolve(id + ".html"); + CharSink sink = asCharSink(path.toFile(), StandardCharsets.UTF_8); + sink.write(message.message()); + } catch (IOException e) { + throw new MessageStoreException("Failed to save message", e); + } + + try { + configFile.save(); + } catch (IOException e) { + throw new MessageStoreException("Failed to save config", e); + } + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java index 2012f31..3369392 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/GetMessage.java
@@ -14,30 +14,20 @@ 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.PluginName; import com.google.gerrit.extensions.restapi.Response; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.server.config.ConfigResource; 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.Date; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.lib.Config; 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"; @@ -45,31 +35,26 @@ private static final Logger log = LoggerFactory.getLogger(GetMessage.class); - private final File cfgFile; - private final Path dataDirPath; - - private volatile FileBasedConfig cfg; + private final MessageStore messageStore; @Inject - public GetMessage( - @PluginName String pluginName, @ConfigFile File cfgFile, @DataDir Path dataDirPath) { - this.dataDirPath = dataDirPath; - this.cfgFile = cfgFile; + public GetMessage(MessageStore messageStore) { + this.messageStore = messageStore; } @Override public Response<MessageOfTheDayInfo> apply(ConfigResource rsrc) { MessageOfTheDayInfo motd = new MessageOfTheDayInfo(); - cfg = new FileBasedConfig(cfgFile, FS.DETECTED); + ConfiguredMessage configuredMessage; try { - cfg.load(); - } catch (ConfigInvalidException | IOException e) { - return null; + configuredMessage = messageStore.getConfiguredMessage(); + } catch (MessageStoreException e) { + log.warn(e.getMessage()); + return Response.none(); } - - String htmlFileId = cfg.getString(SECTION_MESSAGE, null, KEY_ID); - if (Strings.isNullOrEmpty(htmlFileId)) { - log.warn("id not defined, no message will be shown"); + Config cfg = configuredMessage.config(); + String message = configuredMessage.message(); + if (cfg == null || message == null) { return Response.none(); } @@ -92,15 +77,7 @@ return Response.none(); } - try { - motd.html = new String(Files.readAllBytes(dataDirPath.resolve(htmlFileId + ".html")), UTF_8); - } catch (IOException e1) { - log.warn( - String.format( - "No HTML-file was found for message %s, no message will be shown", htmlFileId)); - return Response.none(); - } - + motd.html = message; motd.id = Integer.toString(motd.html.hashCode()); return Response.ok(motd); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageStore.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageStore.java new file mode 100644 index 0000000..1bd00c1 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageStore.java
@@ -0,0 +1,21 @@ +// Copyright (C) 2025 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; + +public interface MessageStore { + ConfiguredMessage getConfiguredMessage() throws MessageStoreException; + + void saveConfiguredMessage(ConfiguredMessage message) throws MessageStoreException; +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageStoreException.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageStoreException.java new file mode 100644 index 0000000..2237071 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/MessageStoreException.java
@@ -0,0 +1,27 @@ +// Copyright (C) 2025 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; + +public class MessageStoreException extends Exception { + private static final long serialVersionUID = 1L; + + public MessageStoreException(String message) { + super(message); + } + + public MessageStoreException(String message, Exception cause) { + super(message, cause); + } +}
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 8ecf5a6..085608b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/Module.java
@@ -44,6 +44,7 @@ @Override protected void configure() { + bind(MessageStore.class).to(FileBasedMessageStore.class); bind(CapabilityDefinition.class) .annotatedWith(Exports.named(UpdateBannerCapability.NAME)) .to(UpdateBannerCapability.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/SetMessage.java b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/SetMessage.java index 4096a5d..39efa69 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/SetMessage.java +++ b/src/main/java/com/googlesource/gerrit/plugins/messageoftheday/SetMessage.java
@@ -16,8 +16,6 @@ import com.google.common.base.Strings; import com.google.common.flogger.FluentLogger; -import com.google.common.io.CharSink; -import com.google.common.io.Files; import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.ResourceConflictException; @@ -30,18 +28,13 @@ import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.inject.Inject; import com.google.inject.Provider; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; public class SetMessage implements RestModifyView<ConfigResource, MessageInput> { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @@ -53,24 +46,19 @@ private static final DateTimeFormatter INPUT_DATE_FORMAT = DateTimeFormatter.ofPattern(INPUT_DATE_FORMAT_PATTERN, Locale.ENGLISH); - private final File cfgFile; - private final Path dataDirPath; + private final MessageStore messageStore; private final ZoneId serverZoneId; private final PermissionBackend permissionBackend; private final UpdateBannerPermission permission; - private volatile FileBasedConfig cfg; - @Inject public SetMessage( - @ConfigFile File cfgFile, - @DataDir Path dataDirPath, + MessageStore messageStore, @GerritPersonIdent Provider<PersonIdent> serverIdent, PermissionBackend permissionBackend, UpdateBannerPermission permission) { - this.dataDirPath = dataDirPath; + this.messageStore = messageStore; this.serverZoneId = serverIdent.get().getZoneId(); - this.cfgFile = cfgFile; this.permission = permission; this.permissionBackend = permissionBackend; } @@ -89,22 +77,21 @@ throw new BadRequestException("message is required"); } - cfg = new FileBasedConfig(cfgFile, FS.DETECTED); + ConfiguredMessage configuredMessage; try { - cfg.load(); - } catch (ConfigInvalidException | IOException e) { - throw new UnprocessableEntityException("plugin cfg is invalid or could not be loaded", e); + configuredMessage = messageStore.getConfiguredMessage(); + } catch (MessageStoreException e) { + throw new UnprocessableEntityException(e.getMessage(), e); } + Config cfg = configuredMessage.config(); + String id = cfg.getString(SECTION_MESSAGE, null, KEY_ID); if (Strings.isNullOrEmpty(id)) { logger.atInfo().log("'id' is not configured in the plugin cfg. Choosing a default id."); - id = "default"; + cfg.setString(SECTION_MESSAGE, null, KEY_ID, "default"); } - Path path = dataDirPath.resolve(id + ".html"); - CharSink sink = Files.asCharSink(path.toFile(), StandardCharsets.UTF_8); - if (input.expiresAt != null) { ZonedDateTime time; try { @@ -122,7 +109,6 @@ null, KEY_EXPIRES_AT, time.format(DateTimeFormatter.ofPattern("yyyyMMdd:HHmm"))); - cfg.setString(SECTION_MESSAGE, null, KEY_ID, id); } else { String expiredAt = cfg.getString(SECTION_MESSAGE, null, KEY_EXPIRES_AT); if (expiredAt == null) { @@ -133,17 +119,9 @@ } try { - sink.write(input.message); - } catch (IOException e) { - throw new UnprocessableEntityException("Failed to save message", e); - } - - if (input.expiresAt != null) { - try { - cfg.save(); - } catch (IOException e) { - throw new UnprocessableEntityException("Failed to save plugin config", e); - } + messageStore.saveConfiguredMessage(ConfiguredMessage.create(cfg, input.message)); + } catch (MessageStoreException e) { + throw new UnprocessableEntityException(e.getMessage(), e); } return Response.ok();