diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalDynamicConfigurationManagerFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalDynamicConfigurationManagerFactory.java new file mode 100644 index 000000000..95c0a03e0 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/LocalDynamicConfigurationManagerFactory.java @@ -0,0 +1,108 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.configuration; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.util.concurrent.ScheduledExecutorService; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; + +import io.dropwizard.util.Resources; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; + +@JsonTypeName("local") +public class LocalDynamicConfigurationManagerFactory implements DynamicConfigurationManagerFactory { + + @JsonProperty + @NotEmpty + private String application; + + @JsonProperty + @NotEmpty + private String environment; + + @JsonProperty + @NotEmpty + private String configuration; + + @JsonProperty + @NotBlank + private String configPath; + + @Override + public DynamicConfigurationManager build(final Class klazz, + final ScheduledExecutorService scheduledExecutorService, final AwsCredentialsProvider awsCredentialsProvider) { + + return new LocalDynamicConfigurationManager<>(configPath, application, environment, configuration, + awsCredentialsProvider, klazz, scheduledExecutorService); + } + + private static class LocalDynamicConfigurationManager extends DynamicConfigurationManager { + + private static final Logger logger = LoggerFactory.getLogger(DynamicConfigurationManager.class); + + private final Path configPath; + private final Class configurationClass; + private T cachedConfig; + private final Instant lastConfigLoadedTime; + + public LocalDynamicConfigurationManager(final String configPath, final String application, final String environment, + final String configurationName, final AwsCredentialsProvider awsCredentialsProvider, + final Class configurationClass, final ScheduledExecutorService scheduledExecutorService) { + + super(application, environment, configurationName, awsCredentialsProvider, configurationClass, + scheduledExecutorService); + + this.configPath = Path.of(Resources.getResource("config").getPath()).resolve(configPath); + this.configurationClass = configurationClass; + this.cachedConfig = null; + this.lastConfigLoadedTime = null; + maybeUpdateConfig(); + if (cachedConfig == null) { + throw new IllegalArgumentException("failed to load initial config"); + } + } + + @Override + public T getConfiguration() { + maybeUpdateConfig(); + return cachedConfig; + } + + @Override + public void start() { + // do nothing + } + + private synchronized void maybeUpdateConfig() { + try { + if (lastConfigLoadedTime != null && + !lastConfigLoadedTime.isBefore(Files.readAttributes(configPath, BasicFileAttributes.class).lastModifiedTime().toInstant())) { + return; + } + String configContents = Files.readString(configPath); + parseConfiguration(configContents, configurationClass).ifPresent(config -> cachedConfig = config); + } catch (Exception e) { + logger.warn("Failed to update configuration", e); + } + } + + } + +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticDynamicConfigurationManagerFactory.java b/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticDynamicConfigurationManagerFactory.java deleted file mode 100644 index d01f49bfb..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/configuration/StaticDynamicConfigurationManagerFactory.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.configuration; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonTypeName; -import java.util.concurrent.ScheduledExecutorService; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; - -@JsonTypeName("static") -public class StaticDynamicConfigurationManagerFactory implements DynamicConfigurationManagerFactory { - - @JsonProperty - @NotEmpty - private String application; - - @JsonProperty - @NotEmpty - private String environment; - - @JsonProperty - @NotEmpty - private String configuration; - - @JsonProperty - @NotBlank - private String staticConfig; - - @Override - public DynamicConfigurationManager build(final Class klazz, - final ScheduledExecutorService scheduledExecutorService, final AwsCredentialsProvider awsCredentialsProvider) { - - return new StaticDynamicConfigurationManager<>(staticConfig, application, environment, configuration, - awsCredentialsProvider, klazz, scheduledExecutorService); - } - - private static class StaticDynamicConfigurationManager extends DynamicConfigurationManager { - - private final T configuration; - - public StaticDynamicConfigurationManager(final String config, final String application, final String environment, - final String configurationName, final AwsCredentialsProvider awsCredentialsProvider, - final Class configurationClass, final ScheduledExecutorService scheduledExecutorService) { - - super(application, environment, configurationName, awsCredentialsProvider, configurationClass, - scheduledExecutorService); - - try { - this.configuration = parseConfiguration(config, configurationClass).orElseThrow(); - } catch (Exception e) { - throw new IllegalArgumentException(e); - } - } - - @Override - public T getConfiguration() { - return configuration; - } - - @Override - public void start() { - // do nothing - } - } -} diff --git a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory index 9bc8ddf4d..4ffece4f8 100644 --- a/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory +++ b/service/src/test/resources/META-INF/services/org.whispersystems.textsecuregcm.configuration.DynamicConfigurationManagerFactory @@ -1 +1 @@ -org.whispersystems.textsecuregcm.configuration.StaticDynamicConfigurationManagerFactory +org.whispersystems.textsecuregcm.configuration.LocalDynamicConfigurationManagerFactory diff --git a/service/src/test/resources/config/test-dynamic.yml b/service/src/test/resources/config/test-dynamic.yml new file mode 100644 index 000000000..c8e6d4d24 --- /dev/null +++ b/service/src/test/resources/config/test-dynamic.yml @@ -0,0 +1,2 @@ +captcha: + scoreFloor: 1.0 diff --git a/service/src/test/resources/config/test.yml b/service/src/test/resources/config/test.yml index 21d7ed23c..274879b87 100644 --- a/service/src/test/resources/config/test.yml +++ b/service/src/test/resources/config/test.yml @@ -311,13 +311,11 @@ backupsZkConfig: serverSecret: secret://backupsZkConfig.serverSecret appConfig: - type: static + type: local application: test environment: test configuration: test - staticConfig: | - captcha: - scoreFloor: 1.0 + configPath: test-dynamic.yml remoteConfig: globalConfig: # keys and values that are given to clients on GET /v1/config