diff --git a/README.md b/README.md
index 872070a7f..9e23f15c6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# th2 common library (Java) (5.13.1)
+# th2 common library (Java) (5.14.0)
## Usage
@@ -511,6 +511,11 @@ dependencies {
## Release notes
+### 5.14.0-dev
+#### Feature:
++ Added common microservice entry point
++ Added configuration provider to common factory
+
### 5.13.1-dev
+ Provided ability to set either of raw body of several dody data to `Event` builder
diff --git a/build.gradle b/build.gradle
index 0a7562505..97b1c3a54 100644
--- a/build.gradle
+++ b/build.gradle
@@ -157,6 +157,7 @@ dependencies {
implementation "io.github.microutils:kotlin-logging:3.0.5"
+ testImplementation 'org.hamcrest:hamcrest:2.2'
testImplementation 'javax.annotation:javax.annotation-api:1.3.2'
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testImplementation "org.mockito.kotlin:mockito-kotlin:5.2.1"
diff --git a/gradle.properties b/gradle.properties
index 2c553bc74..f8a2b4afb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-release_version=5.13.1
+release_version=5.14.0
kotlin_version=1.8.22
description='th2 common library (Java)'
vcs_url=https://github.com/th2-net/th2-common-j
diff --git a/src/main/java/com/exactpro/th2/common/schema/factory/AbstractCommonFactory.java b/src/main/java/com/exactpro/th2/common/schema/factory/AbstractCommonFactory.java
index 170920614..8b794f4c4 100644
--- a/src/main/java/com/exactpro/th2/common/schema/factory/AbstractCommonFactory.java
+++ b/src/main/java/com/exactpro/th2/common/schema/factory/AbstractCommonFactory.java
@@ -33,13 +33,16 @@
import com.exactpro.th2.common.metrics.PrometheusConfiguration;
import com.exactpro.th2.common.schema.box.configuration.BoxConfiguration;
import com.exactpro.th2.common.schema.configuration.ConfigurationManager;
+import com.exactpro.th2.common.schema.configuration.impl.JsonConfigurationProvider;
import com.exactpro.th2.common.schema.cradle.CradleConfidentialConfiguration;
import com.exactpro.th2.common.schema.cradle.CradleNonConfidentialConfiguration;
import com.exactpro.th2.common.schema.dictionary.DictionaryType;
+import com.exactpro.th2.common.schema.event.EventBatchRouter;
import com.exactpro.th2.common.schema.exception.CommonFactoryException;
import com.exactpro.th2.common.schema.grpc.configuration.GrpcConfiguration;
import com.exactpro.th2.common.schema.grpc.configuration.GrpcRouterConfiguration;
import com.exactpro.th2.common.schema.grpc.router.GrpcRouter;
+import com.exactpro.th2.common.schema.grpc.router.impl.DefaultGrpcRouter;
import com.exactpro.th2.common.schema.message.MessageRouter;
import com.exactpro.th2.common.schema.message.MessageRouterContext;
import com.exactpro.th2.common.schema.message.MessageRouterMonitor;
@@ -55,24 +58,22 @@
import com.exactpro.th2.common.schema.message.impl.rabbitmq.connection.ConnectionManager;
import com.exactpro.th2.common.schema.message.impl.rabbitmq.custom.MessageConverter;
import com.exactpro.th2.common.schema.message.impl.rabbitmq.custom.RabbitCustomRouter;
+import com.exactpro.th2.common.schema.message.impl.rabbitmq.group.RabbitMessageGroupBatchRouter;
+import com.exactpro.th2.common.schema.message.impl.rabbitmq.notification.NotificationEventBatchRouter;
+import com.exactpro.th2.common.schema.message.impl.rabbitmq.parsed.RabbitParsedBatchRouter;
+import com.exactpro.th2.common.schema.message.impl.rabbitmq.raw.RabbitRawBatchRouter;
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.GroupBatch;
import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.TransportGroupBatchRouter;
-import com.exactpro.th2.common.schema.strategy.route.json.RoutingStrategyModule;
import com.exactpro.th2.common.schema.util.Log4jConfigUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.fasterxml.jackson.module.kotlin.KotlinFeature;
-import com.fasterxml.jackson.module.kotlin.KotlinModule;
import io.prometheus.client.exporter.HTTPServer;
import io.prometheus.client.hotspot.DefaultExports;
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.text.StringSubstitutor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
@@ -94,7 +95,6 @@
import static com.exactpro.th2.common.schema.factory.LazyProvider.lazy;
import static com.exactpro.th2.common.schema.factory.LazyProvider.lazyAutocloseable;
import static java.util.Objects.requireNonNull;
-import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
/**
* Class for load JSON schema configuration and create {@link GrpcRouter} and {@link MessageRouter}
@@ -111,25 +111,9 @@ public abstract class AbstractCommonFactory implements AutoCloseable {
protected static final Path LOG4J_PROPERTIES_DEFAULT_PATH = Path.of("/var/th2/config");
protected static final String LOG4J2_PROPERTIES_NAME = "log4j2.properties";
- public static final ObjectMapper MAPPER = new ObjectMapper();
-
- static {
- MAPPER.registerModules(
- new KotlinModule.Builder()
- .withReflectionCacheSize(512)
- .configure(KotlinFeature.NullToEmptyCollection, false)
- .configure(KotlinFeature.NullToEmptyMap, false)
- .configure(KotlinFeature.NullIsSameAsDefault, false)
- .configure(KotlinFeature.SingletonSupport, false)
- .configure(KotlinFeature.StrictNullChecks, false)
- .build(),
- new RoutingStrategyModule(MAPPER),
- new JavaTimeModule()
- );
- }
-
+ static final String CUSTOM_CFG_ALIAS = "custom";
+ public static final ObjectMapper MAPPER = JsonConfigurationProvider.MAPPER;
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCommonFactory.class);
- private final StringSubstitutor stringSubstitutor;
private final Class extends MessageRouter> messageRouterParsedBatchClass;
private final Class extends MessageRouter> messageRouterRawBatchClass;
@@ -168,6 +152,15 @@ public abstract class AbstractCommonFactory implements AutoCloseable {
configureLogger();
}
+ protected AbstractCommonFactory() {
+ messageRouterParsedBatchClass = RabbitParsedBatchRouter.class ;
+ messageRouterRawBatchClass = RabbitRawBatchRouter.class;
+ messageRouterMessageGroupBatchClass = RabbitMessageGroupBatchRouter.class;
+ eventBatchRouterClass = EventBatchRouter.class;
+ grpcRouterClass = DefaultGrpcRouter.class;
+ notificationEventBatchRouterClass = NotificationEventBatchRouter.class;
+ }
+
/**
* Create factory with non-default implementations schema classes
*
@@ -180,7 +173,6 @@ public AbstractCommonFactory(FactorySettings settings) {
eventBatchRouterClass = settings.getEventBatchRouterClass();
grpcRouterClass = settings.getGrpcRouterClass();
notificationEventBatchRouterClass = settings.getNotificationEventBatchRouterClass();
- stringSubstitutor = new StringSubstitutor(key -> defaultIfBlank(settings.getVariables().get(key), System.getenv(key)));
}
public void start() {
@@ -321,11 +313,11 @@ public MessageRouter getCustomMessageRouter(Class messageClass) {
}
/**
- * @return Configuration by specified path
+ * @return Configuration by specified alias
* @throws IllegalStateException if can not read configuration
*/
- public T getConfiguration(Path configPath, Class configClass, ObjectMapper customObjectMapper) {
- return getConfigurationManager().loadConfiguration(customObjectMapper, stringSubstitutor, configClass, configPath, false);
+ public T getConfiguration(String configAlias, Class configClass, ObjectMapper customObjectMapper) {
+ return getConfigurationManager().loadConfiguration(configClass, configAlias, false);
}
/**
@@ -336,7 +328,7 @@ public T getConfiguration(Path configPath, Class configClass, ObjectMappe
* @return configuration object
*/
protected T getConfigurationOrLoad(Class configClass, boolean optional) {
- return getConfigurationManager().getConfigurationOrLoad(MAPPER, stringSubstitutor, configClass, optional);
+ return getConfigurationManager().getConfigurationOrLoad(configClass, optional);
}
public RabbitMQConfiguration getRabbitMqConfiguration() {
@@ -352,7 +344,7 @@ public MessageRouterConfiguration getMessageRouterConfiguration() {
}
public GrpcConfiguration getGrpcConfiguration() {
- return getConfigurationManager().getConfigurationOrLoad(MAPPER, stringSubstitutor, GrpcConfiguration.class, false);
+ return getConfigurationManager().getConfigurationOrLoad(GrpcConfiguration.class, false);
}
public GrpcRouterConfiguration getGrpcRouterConfiguration() {
@@ -510,17 +502,7 @@ private CradleManager createCradleManager() {
* @throws IllegalStateException if can not read configuration
*/
public T getCustomConfiguration(Class confClass, ObjectMapper customObjectMapper) {
- File configFile = getPathToCustomConfiguration().toFile();
- if (!configFile.exists()) {
- try {
- return confClass.getConstructor().newInstance();
- } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
- NoSuchMethodException e) {
- return null;
- }
- }
-
- return getConfiguration(getPathToCustomConfiguration(), confClass, customObjectMapper);
+ return getConfiguration(CUSTOM_CFG_ALIAS, confClass, customObjectMapper);
}
/**
@@ -569,7 +551,8 @@ public T getCustomConfiguration(Class confClass) {
* @param dictionaryType desired type of dictionary
* @return Dictionary as {@link InputStream}
* @throws IllegalStateException if can not read dictionary
- * @deprecated Dictionary types will be removed in future releases of infra, use alias instead
+ * @deprecated Dictionary types will be removed in future releases of infra, use alias instead.
+ * Please use {@link #loadDictionary(String)}
*/
@Deprecated(since = "3.33.0", forRemoval = true)
public abstract InputStream readDictionary(DictionaryType dictionaryType);
@@ -606,25 +589,6 @@ private EventID createRootEventID() throws IOException {
protected abstract ConfigurationManager getConfigurationManager();
- /**
- * @return Path to custom configuration
- */
- protected abstract Path getPathToCustomConfiguration();
-
- /**
- * @return Path to dictionaries with type dir
- */
- @Deprecated(since = "3.33.0", forRemoval = true)
- protected abstract Path getPathToDictionaryTypesDir();
-
- /**
- * @return Path to dictionaries with alias dir
- */
- protected abstract Path getPathToDictionaryAliasesDir();
-
- @Deprecated(since = "3.33.0", forRemoval = true)
- protected abstract Path getOldPathToDictionariesDir();
-
/**
* @return Context for all routers except event router
*/
diff --git a/src/main/java/com/exactpro/th2/common/schema/factory/CommonFactory.java b/src/main/java/com/exactpro/th2/common/schema/factory/CommonFactory.java
index 8fe4353c3..e0b025bfe 100644
--- a/src/main/java/com/exactpro/th2/common/schema/factory/CommonFactory.java
+++ b/src/main/java/com/exactpro/th2/common/schema/factory/CommonFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2023 Exactpro (Exactpro Systems Limited)
+ * Copyright 2020-2024 Exactpro (Exactpro Systems Limited)
* 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
@@ -19,6 +19,11 @@
import com.exactpro.th2.common.metrics.PrometheusConfiguration;
import com.exactpro.th2.common.schema.box.configuration.BoxConfiguration;
import com.exactpro.th2.common.schema.configuration.ConfigurationManager;
+import com.exactpro.th2.common.schema.configuration.IConfigurationProvider;
+import com.exactpro.th2.common.schema.configuration.IDictionaryProvider;
+import com.exactpro.th2.common.schema.configuration.impl.DictionaryKind;
+import com.exactpro.th2.common.schema.configuration.impl.DictionaryProvider;
+import com.exactpro.th2.common.schema.configuration.impl.JsonConfigurationProvider;
import com.exactpro.th2.common.schema.cradle.CradleConfidentialConfiguration;
import com.exactpro.th2.common.schema.cradle.CradleNonConfidentialConfiguration;
import com.exactpro.th2.common.schema.dictionary.DictionaryType;
@@ -42,7 +47,6 @@
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
-import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@@ -50,7 +54,6 @@
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
-import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -60,18 +63,14 @@
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
+import java.util.EnumMap;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import static com.exactpro.th2.common.schema.util.ArchiveUtils.getGzipBase64StringDecoder;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
-import static java.util.Objects.requireNonNullElseGet;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
/**
@@ -83,16 +82,15 @@ public class CommonFactory extends AbstractCommonFactory {
public static final String TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY = TH2_COMMON_SYSTEM_PROPERTY + '.' + "configuration-directory";
static final Path CONFIG_DEFAULT_PATH = Path.of("/var/th2/config/");
- static final String RABBIT_MQ_FILE_NAME = "rabbitMQ.json";
- static final String ROUTER_MQ_FILE_NAME = "mq.json";
- static final String GRPC_FILE_NAME = "grpc.json";
- static final String ROUTER_GRPC_FILE_NAME = "grpc_router.json";
- static final String CRADLE_CONFIDENTIAL_FILE_NAME = "cradle.json";
- static final String PROMETHEUS_FILE_NAME = "prometheus.json";
- static final String CUSTOM_FILE_NAME = "custom.json";
- static final String BOX_FILE_NAME = "box.json";
- static final String CONNECTION_MANAGER_CONF_FILE_NAME = "mq_router.json";
- static final String CRADLE_NON_CONFIDENTIAL_FILE_NAME = "cradle_manager.json";
+ static final String RABBIT_MQ_CFG_ALIAS = "rabbitMQ";
+ static final String ROUTER_MQ_CFG_ALIAS = "mq";
+ static final String GRPC_CFG_ALIAS = "grpc";
+ static final String ROUTER_GRPC_CFG_ALIAS = "grpc_router";
+ static final String CRADLE_CONFIDENTIAL_CFG_ALIAS = "cradle";
+ static final String PROMETHEUS_CFG_ALIAS = "prometheus";
+ static final String BOX_CFG_ALIAS = "box";
+ static final String CONNECTION_MANAGER_CFG_ALIAS = "mq_router";
+ static final String CRADLE_NON_CONFIDENTIAL_CFG_ALIAS = "cradle_manager";
/** @deprecated please use {@link #DICTIONARY_ALIAS_DIR_NAME} */
@Deprecated
@@ -113,52 +111,43 @@ public class CommonFactory extends AbstractCommonFactory {
private static final String CRADLE_MANAGER_CONFIG_MAP = "cradle-manager";
private static final String LOGGING_CONFIG_MAP = "logging-config";
- private final Path custom;
- private final Path dictionaryTypesDir;
- private final Path dictionaryAliasesDir;
- private final Path oldDictionariesDir;
+ final IConfigurationProvider configurationProvider;
+ final IDictionaryProvider dictionaryProvider;
final ConfigurationManager configurationManager;
private static final Logger LOGGER = LoggerFactory.getLogger(CommonFactory.class.getName());
- public CommonFactory(FactorySettings settings) {
- super(settings);
- custom = defaultPathIfNull(settings.getCustom(), CUSTOM_FILE_NAME);
- dictionaryTypesDir = defaultPathIfNull(settings.getDictionaryTypesDir(), DICTIONARY_TYPE_DIR_NAME);
- dictionaryAliasesDir = defaultPathIfNull(settings.getDictionaryAliasesDir(), DICTIONARY_ALIAS_DIR_NAME);
- oldDictionariesDir = requireNonNullElseGet(settings.getOldDictionariesDir(), CommonFactory::getConfigPath);
- configurationManager = createConfigurationManager(settings);
+ private CommonFactory(@NotNull IConfigurationProvider configurationProvider, @NotNull IDictionaryProvider dictionaryProvider) {
+ super();
+ this.dictionaryProvider = requireNonNull(dictionaryProvider, "Dictionary provider can't be null");
+ this.configurationProvider = requireNonNull(configurationProvider, "Configuration provider can't be null");
+ configurationManager = createConfigurationManager(configurationProvider);
start();
}
- public CommonFactory() {
- this(new FactorySettings());
+ @Deprecated(since = "6", forRemoval = true)
+ public CommonFactory(FactorySettings settings) {
+ this(createConfigurationProvider(settings), createDictionaryProvider(settings));
}
- @Override
- protected Path getPathToCustomConfiguration() {
- return custom;
+ public CommonFactory() {
+ this(new FactorySettings());
}
@Override
- protected Path getPathToDictionaryTypesDir() {
- return dictionaryTypesDir;
+ protected ConfigurationManager getConfigurationManager() {
+ return configurationManager;
}
- @Override
- protected Path getPathToDictionaryAliasesDir() {
- return dictionaryAliasesDir;
+ public IConfigurationProvider getConfigurationProvider() {
+ return configurationProvider;
}
- @Override
- protected Path getOldPathToDictionariesDir() {
- return oldDictionariesDir;
+ public static CommonFactory createFromProvider(@NotNull IConfigurationProvider configurationProvider,
+ @NotNull IDictionaryProvider dictionaryProvider) {
+ return new CommonFactory(configurationProvider, dictionaryProvider);
}
- @Override
- protected ConfigurationManager getConfigurationManager() {
- return configurationManager;
- }
/**
* Create {@link CommonFactory} from command line arguments
*
@@ -236,7 +225,7 @@ public static CommonFactory createFromArguments(String... args) {
try {
CommandLine cmd = new DefaultParser().parse(options, args);
- Path configs = getConfigPath(cmd.getOptionValue(configOption.getLongOpt()));
+ Path configs = toPath(cmd.getOptionValue(configOption.getLongOpt()));
if (cmd.hasOption(namespaceOption.getLongOpt()) && cmd.hasOption(boxNameOption.getLongOpt())) {
String namespace = cmd.getOptionValue(namespaceOption.getLongOpt());
@@ -267,24 +256,26 @@ public static CommonFactory createFromArguments(String... args) {
return createFromKubernetes(namespace, boxName, contextName, dictionaries);
}
- if (!CONFIG_DEFAULT_PATH.equals(configs)) {
- configureLogger(configs);
- }
FactorySettings settings = new FactorySettings();
- settings.setRabbitMQ(calculatePath(cmd, rabbitConfigurationOption, configs, RABBIT_MQ_FILE_NAME));
- settings.setRouterMQ(calculatePath(cmd, messageRouterConfigurationOption, configs, ROUTER_MQ_FILE_NAME));
- settings.setConnectionManagerSettings(calculatePath(cmd, connectionManagerConfigurationOption, configs, CONNECTION_MANAGER_CONF_FILE_NAME));
- settings.setGrpc(calculatePath(cmd, grpcConfigurationOption, grpcRouterConfigurationOption, configs, GRPC_FILE_NAME));
- settings.setRouterGRPC(calculatePath(cmd, grpcRouterConfigOption, configs, ROUTER_GRPC_FILE_NAME));
- settings.setCradleConfidential(calculatePath(cmd, cradleConfidentialConfigurationOption, cradleConfigurationOption, configs, CRADLE_CONFIDENTIAL_FILE_NAME));
- settings.setCradleNonConfidential(calculatePath(cmd, cradleManagerConfigurationOption, configs, CRADLE_NON_CONFIDENTIAL_FILE_NAME));
- settings.setPrometheus(calculatePath(cmd, prometheusConfigurationOption, configs, PROMETHEUS_FILE_NAME));
- settings.setBoxConfiguration(calculatePath(cmd, boxConfigurationOption, configs, BOX_FILE_NAME));
- settings.setCustom(calculatePath(cmd, customConfigurationOption, configs, CUSTOM_FILE_NAME));
- settings.setDictionaryTypesDir(calculatePath(cmd, dictionariesDirOption, configs, DICTIONARY_TYPE_DIR_NAME));
- settings.setDictionaryAliasesDir(calculatePath(cmd, dictionariesDirOption, configs, DICTIONARY_ALIAS_DIR_NAME));
- String oldDictionariesDir = cmd.getOptionValue(dictionariesDirOption.getLongOpt());
- settings.setOldDictionariesDir(oldDictionariesDir == null ? configs : Path.of(oldDictionariesDir));
+ if (configs != null) {
+ settings.setBaseConfigDir(configs);
+ if (!CONFIG_DEFAULT_PATH.equals(configs)) {
+ configureLogger(configs);
+ }
+ }
+ settings.setRabbitMQ(toPath(cmd.getOptionValue(rabbitConfigurationOption.getLongOpt())));
+ settings.setRouterMQ(toPath(cmd.getOptionValue(messageRouterConfigurationOption.getLongOpt())));
+ settings.setConnectionManagerSettings(toPath(cmd.getOptionValue(connectionManagerConfigurationOption.getLongOpt())));
+ settings.setGrpc(toPath(defaultIfNull(cmd.getOptionValue(grpcConfigurationOption.getLongOpt()), cmd.getOptionValue(grpcRouterConfigurationOption.getLongOpt()))));
+ settings.setRouterGRPC(toPath(cmd.getOptionValue(grpcRouterConfigOption.getLongOpt())));
+ settings.setCradleConfidential(toPath(cmd.getOptionValue(cradleConfidentialConfigurationOption.getLongOpt())));
+ settings.setCradleNonConfidential(toPath(defaultIfNull(cmd.getOptionValue(cradleManagerConfigurationOption.getLongOpt()), cmd.getOptionValue(cradleConfigurationOption.getLongOpt()))));
+ settings.setPrometheus(toPath(cmd.getOptionValue(prometheusConfigurationOption.getLongOpt())));
+ settings.setBoxConfiguration(toPath(cmd.getOptionValue(boxConfigurationOption.getLongOpt())));
+ settings.setCustom(toPath(cmd.getOptionValue(customConfigurationOption.getLongOpt())));
+ settings.setDictionaryTypesDir(toPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt())));
+ settings.setDictionaryAliasesDir(toPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt())));
+ settings.setOldDictionariesDir(toPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt())));
return new CommonFactory(settings);
} catch (ParseException e) {
@@ -299,6 +290,7 @@ public static CommonFactory createFromArguments(String... args) {
* @param boxName - the name of the target th2 box placed in the specified namespace in Kubernetes
* @return CommonFactory with set path
*/
+ @Deprecated(since = "6", forRemoval = true)
public static CommonFactory createFromKubernetes(String namespace, String boxName) {
return createFromKubernetes(namespace, boxName, null);
}
@@ -311,6 +303,7 @@ public static CommonFactory createFromKubernetes(String namespace, String boxNam
* @param contextName - context name to choose the context from Kube config
* @return CommonFactory with set path
*/
+ @Deprecated(since = "6", forRemoval = true)
public static CommonFactory createFromKubernetes(String namespace, String boxName, @Nullable String contextName) {
return createFromKubernetes(namespace, boxName, contextName, emptyMap());
}
@@ -331,7 +324,7 @@ public static CommonFactory createFromKubernetes(String namespace, String boxNam
Path dictionaryTypePath = configPath.resolve(DICTIONARY_TYPE_DIR_NAME);
Path dictionaryAliasPath = configPath.resolve(DICTIONARY_ALIAS_DIR_NAME);
- Path boxConfigurationPath = configPath.resolve(BOX_FILE_NAME);
+ Path boxConfigurationPath = configPath.resolve(BOX_CFG_ALIAS);
FactorySettings settings = new FactorySettings();
@@ -398,21 +391,21 @@ public static CommonFactory createFromKubernetes(String namespace, String boxNam
configureLogger(configPath);
}
- settings.setRabbitMQ(writeFile(configPath, RABBIT_MQ_FILE_NAME, rabbitMqData));
- settings.setRouterMQ(writeFile(configPath, ROUTER_MQ_FILE_NAME, boxData));
- settings.setConnectionManagerSettings(writeFile(configPath, CONNECTION_MANAGER_CONF_FILE_NAME, boxData));
- settings.setGrpc(writeFile(configPath, GRPC_FILE_NAME, boxData));
- settings.setRouterGRPC(writeFile(configPath, ROUTER_GRPC_FILE_NAME, boxData));
- settings.setCradleConfidential(writeFile(configPath, CRADLE_CONFIDENTIAL_FILE_NAME, cradleConfidential));
- settings.setCradleNonConfidential(writeFile(configPath, CRADLE_NON_CONFIDENTIAL_FILE_NAME, cradleNonConfidential));
- settings.setPrometheus(writeFile(configPath, PROMETHEUS_FILE_NAME, boxData));
- settings.setCustom(writeFile(configPath, CUSTOM_FILE_NAME, boxData));
+ settings.setRabbitMQ(writeFile(configPath, RABBIT_MQ_CFG_ALIAS, rabbitMqData));
+ settings.setRouterMQ(writeFile(configPath, ROUTER_MQ_CFG_ALIAS, boxData));
+ settings.setConnectionManagerSettings(writeFile(configPath, CONNECTION_MANAGER_CFG_ALIAS, boxData));
+ settings.setGrpc(writeFile(configPath, GRPC_CFG_ALIAS, boxData));
+ settings.setRouterGRPC(writeFile(configPath, ROUTER_GRPC_CFG_ALIAS, boxData));
+ settings.setCradleConfidential(writeFile(configPath, CRADLE_CONFIDENTIAL_CFG_ALIAS, cradleConfidential));
+ settings.setCradleNonConfidential(writeFile(configPath, CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, cradleNonConfidential));
+ settings.setPrometheus(writeFile(configPath, PROMETHEUS_CFG_ALIAS, boxData));
+ settings.setCustom(writeFile(configPath, CUSTOM_CFG_ALIAS, boxData));
settings.setBoxConfiguration(boxConfigurationPath);
settings.setDictionaryTypesDir(dictionaryTypePath);
settings.setDictionaryAliasesDir(dictionaryAliasPath);
- String boxConfig = boxData.get(BOX_FILE_NAME);
+ String boxConfig = boxData.get(BOX_CFG_ALIAS);
if (boxConfig == null) {
writeToJson(boxConfigurationPath, box);
@@ -432,125 +425,49 @@ public static CommonFactory createFromKubernetes(String namespace, String boxNam
@Override
public InputStream loadSingleDictionary() {
- Path dictionaryFolder = getPathToDictionaryAliasesDir();
- try {
- LOGGER.debug("Loading dictionary from folder: {}", dictionaryFolder);
- List dictionaries = null;
- if (Files.isDirectory(dictionaryFolder)) {
- try (Stream files = Files.list(dictionaryFolder)) {
- dictionaries = files.filter(Files::isRegularFile).collect(Collectors.toList());
- }
- }
-
- if (dictionaries==null || dictionaries.isEmpty()) {
- throw new IllegalStateException("No dictionary at path: " + dictionaryFolder.toAbsolutePath());
- } else if (dictionaries.size() > 1) {
- throw new IllegalStateException("Found several dictionaries at path: " + dictionaryFolder.toAbsolutePath());
- }
-
- var targetDictionary = dictionaries.get(0);
-
- return new ByteArrayInputStream(getGzipBase64StringDecoder().decode(Files.readString(targetDictionary)));
- } catch (IOException e) {
- throw new IllegalStateException("Can not read dictionary from path: " + dictionaryFolder.toAbsolutePath(), e);
- }
+ return dictionaryProvider.load();
}
@Override
public Set getDictionaryAliases() {
- Path dictionaryFolder = getPathToDictionaryAliasesDir();
- try {
- if (!Files.isDirectory(dictionaryFolder)) {
- return Set.of();
- }
-
- try (Stream files = Files.list(dictionaryFolder)) {
- return files
- .filter(Files::isRegularFile)
- .map(dictionary -> FilenameUtils.removeExtension(dictionary.getFileName().toString()))
- .collect(Collectors.toSet());
- }
- } catch (IOException e) {
- throw new IllegalStateException("Can not get dictionaries aliases from path: " + dictionaryFolder.toAbsolutePath(), e);
- }
+ return dictionaryProvider.aliases();
}
@Override
public InputStream loadDictionary(String alias) {
- Path dictionaryFolder = getPathToDictionaryAliasesDir();
- try {
- LOGGER.debug("Loading dictionary by alias ({}) from folder: {}", alias, dictionaryFolder);
- List dictionaries = null;
-
- if (Files.isDirectory(dictionaryFolder)) {
- try (Stream files = Files.list(dictionaryFolder)) {
- dictionaries = files
- .filter(Files::isRegularFile)
- .filter(path -> FilenameUtils.removeExtension(path.getFileName().toString()).equalsIgnoreCase(alias))
- .collect(Collectors.toList());
- }
- }
-
- if (dictionaries==null || dictionaries.isEmpty()) {
- throw new IllegalStateException("No dictionary was found by alias '" + alias + "' at path: " + dictionaryFolder.toAbsolutePath());
- } else if (dictionaries.size() > 1) {
- throw new IllegalStateException("Found several dictionaries by alias '" + alias + "' at path: " + dictionaryFolder.toAbsolutePath());
- }
-
- return new ByteArrayInputStream(getGzipBase64StringDecoder().decode(Files.readString(dictionaries.get(0))));
- } catch (IOException e) {
- throw new IllegalStateException("Can not read dictionary '" + alias + "' from path: " + dictionaryFolder.toAbsolutePath(), e);
- }
+ return dictionaryProvider.load(alias);
}
@Override
public InputStream readDictionary() {
- return readDictionary(DictionaryType.MAIN);
+ return dictionaryProvider.load(DictionaryType.MAIN);
}
@Override
public InputStream readDictionary(DictionaryType dictionaryType) {
- try {
- List dictionaries = null;
- Path typeFolder = dictionaryType.getDictionary(getPathToDictionaryTypesDir());
- if (Files.isDirectory(typeFolder)) {
- try (Stream files = Files.list(typeFolder)) {
- dictionaries = files.filter(Files::isRegularFile)
- .collect(Collectors.toList());
- }
- }
-
- // Find with old format
- Path oldFolder = getOldPathToDictionariesDir();
- if ((dictionaries == null || dictionaries.isEmpty()) && Files.isDirectory(oldFolder)) {
- try (Stream files = Files.list(oldFolder)) {
- dictionaries = files.filter(path -> Files.isRegularFile(path) && path.getFileName().toString().contains(dictionaryType.name()))
- .collect(Collectors.toList());
- }
- }
+ return dictionaryProvider.load(dictionaryType);
+ }
- Path dictionaryAliasFolder = getPathToDictionaryAliasesDir();
- if ((dictionaries == null || dictionaries.isEmpty()) && Files.isDirectory(dictionaryAliasFolder)) {
- try (Stream files = Files.list(dictionaryAliasFolder)) {
- dictionaries = files.filter(Files::isRegularFile).filter(path -> FilenameUtils.removeExtension(path.getFileName().toString()).equalsIgnoreCase(dictionaryType.name())).collect(Collectors.toList());
- }
- }
+ static @NotNull Path getConfigPath() {
+ return getConfigPath(null);
+ }
- if (dictionaries == null || dictionaries.isEmpty()) {
- throw new IllegalStateException("No dictionary found with type '" + dictionaryType + "'");
- } else if (dictionaries.size() > 1) {
- throw new IllegalStateException("Found several dictionaries satisfying the '" + dictionaryType + "' type");
+ /**
+ * Priority:
+ * 1. passed via commandline arguments
+ * 2. {@value #TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY} system property
+ * 3. {@link #CONFIG_DEFAULT_PATH} default value
+ */
+ static @NotNull Path getConfigPath(@Nullable Path basePath) {
+ if (basePath != null) {
+ if (Files.exists(basePath) && Files.isDirectory(basePath)) {
+ return basePath;
}
-
- var targetDictionary = dictionaries.get(0);
-
- return new ByteArrayInputStream(getGzipBase64StringDecoder().decode(Files.readString(targetDictionary)));
- } catch (IOException e) {
- throw new IllegalStateException("Can not read dictionary", e);
+ LOGGER.warn("'{}' config directory passed via CMD doesn't exist or it is not a directory", basePath);
+ } else {
+ LOGGER.debug("Skipped blank CMD path for configs directory");
}
- }
- static @NotNull Path getConfigPath() {
String pathString = System.getProperty(TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY);
if (pathString != null) {
Path path = Paths.get(pathString);
@@ -570,21 +487,6 @@ public InputStream readDictionary(DictionaryType dictionaryType) {
return CONFIG_DEFAULT_PATH;
}
- static @NotNull Path getConfigPath(@Nullable String cmdPath) {
- String pathString = StringUtils.trim(cmdPath);
- if (pathString != null) {
- Path path = Paths.get(pathString);
- if (Files.exists(path) && Files.isDirectory(path)) {
- return path;
- }
- LOGGER.warn("'{}' config directory passed via CMD doesn't exist or it is not a directory", cmdPath);
- } else {
- LOGGER.debug("Skipped blank CMD path for configs directory");
- }
-
- return getConfigPath();
- }
-
private static Path writeFile(Path configPath, String fileName, Map configMap) throws IOException {
Path file = configPath.resolve(fileName);
writeFile(file, configMap.get(fileName));
@@ -649,23 +551,49 @@ private static void createDirectory(Path dir) throws IOException {
}
}
- private static ConfigurationManager createConfigurationManager(FactorySettings settings) {
- Map, Path> paths = new HashMap<>();
- paths.put(RabbitMQConfiguration.class, defaultPathIfNull(settings.getRabbitMQ(), RABBIT_MQ_FILE_NAME));
- paths.put(MessageRouterConfiguration.class, defaultPathIfNull(settings.getRouterMQ(), ROUTER_MQ_FILE_NAME));
- paths.put(ConnectionManagerConfiguration.class, defaultPathIfNull(settings.getConnectionManagerSettings(), CONNECTION_MANAGER_CONF_FILE_NAME));
- paths.put(GrpcConfiguration.class, defaultPathIfNull(settings.getGrpc(), GRPC_FILE_NAME));
- paths.put(GrpcRouterConfiguration.class, defaultPathIfNull(settings.getRouterGRPC(), ROUTER_GRPC_FILE_NAME));
- paths.put(CradleConfidentialConfiguration.class, defaultPathIfNull(settings.getCradleConfidential(), CRADLE_CONFIDENTIAL_FILE_NAME));
- paths.put(CradleNonConfidentialConfiguration.class, defaultPathIfNull(settings.getCradleNonConfidential(), CRADLE_NON_CONFIDENTIAL_FILE_NAME));
- paths.put(CassandraStorageSettings.class, defaultPathIfNull(settings.getCradleNonConfidential(), CRADLE_NON_CONFIDENTIAL_FILE_NAME));
- paths.put(PrometheusConfiguration.class, defaultPathIfNull(settings.getPrometheus(), PROMETHEUS_FILE_NAME));
- paths.put(BoxConfiguration.class, defaultPathIfNull(settings.getBoxConfiguration(), BOX_FILE_NAME));
- return new ConfigurationManager(paths);
+ private static IDictionaryProvider createDictionaryProvider(FactorySettings settings) {
+ Map paths = new EnumMap<>(DictionaryKind.class);
+ putIfNotNull(paths, DictionaryKind.OLD, settings.getOldDictionariesDir());
+ putIfNotNull(paths, DictionaryKind.TYPE, settings.getDictionaryTypesDir());
+ putIfNotNull(paths, DictionaryKind.ALIAS, settings.getDictionaryAliasesDir());
+ return new DictionaryProvider(defaultIfNull(settings.getBaseConfigDir(), getConfigPath()), paths);
}
- private static Path defaultPathIfNull(Path path, String name) {
- return path == null ? getConfigPath().resolve(name) : path;
+ private static IConfigurationProvider createConfigurationProvider(FactorySettings settings) {
+ Map paths = new HashMap<>();
+ putIfNotNull(paths, CUSTOM_CFG_ALIAS, settings.getCustom());
+ putIfNotNull(paths, RABBIT_MQ_CFG_ALIAS, settings.getRabbitMQ());
+ putIfNotNull(paths, ROUTER_MQ_CFG_ALIAS, settings.getRouterMQ());
+ putIfNotNull(paths, CONNECTION_MANAGER_CFG_ALIAS, settings.getConnectionManagerSettings());
+ putIfNotNull(paths, GRPC_CFG_ALIAS, settings.getGrpc());
+ putIfNotNull(paths, ROUTER_GRPC_CFG_ALIAS, settings.getRouterGRPC());
+ putIfNotNull(paths, CRADLE_CONFIDENTIAL_CFG_ALIAS, settings.getCradleConfidential());
+ putIfNotNull(paths, CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, settings.getCradleNonConfidential());
+ putIfNotNull(paths, PROMETHEUS_CFG_ALIAS, settings.getPrometheus());
+ putIfNotNull(paths, BOX_CFG_ALIAS, settings.getBoxConfiguration());
+ return new JsonConfigurationProvider(defaultIfNull(settings.getBaseConfigDir(), getConfigPath()), paths);
+ }
+ private static ConfigurationManager createConfigurationManager(IConfigurationProvider configurationProvider) {
+ Map, String> paths = new HashMap<>();
+ paths.put(RabbitMQConfiguration.class, RABBIT_MQ_CFG_ALIAS);
+ paths.put(MessageRouterConfiguration.class, ROUTER_MQ_CFG_ALIAS);
+ paths.put(ConnectionManagerConfiguration.class, CONNECTION_MANAGER_CFG_ALIAS);
+ paths.put(GrpcConfiguration.class, GRPC_CFG_ALIAS);
+ paths.put(GrpcRouterConfiguration.class, ROUTER_GRPC_CFG_ALIAS);
+ paths.put(CradleConfidentialConfiguration.class, CRADLE_CONFIDENTIAL_CFG_ALIAS);
+ paths.put(CradleNonConfidentialConfiguration.class, CRADLE_NON_CONFIDENTIAL_CFG_ALIAS);
+ paths.put(CassandraStorageSettings.class, CRADLE_NON_CONFIDENTIAL_CFG_ALIAS);
+ paths.put(PrometheusConfiguration.class, PROMETHEUS_CFG_ALIAS);
+ paths.put(BoxConfiguration.class, BOX_CFG_ALIAS);
+ return new ConfigurationManager(configurationProvider, paths);
+ }
+
+ private static void putIfNotNull(@NotNull Map paths, @NotNull T key, @Nullable Path path) {
+ requireNonNull(paths, "'Paths' can't be null");
+ requireNonNull(key, "'Key' can't be null");
+ if (path != null) {
+ paths.put(key, path);
+ }
}
private static void writeFile(Path path, String data) throws IOException {
@@ -687,15 +615,8 @@ private static Option createLongOption(Options options, String optionName) {
return option;
}
- private static Path calculatePath(String path, @NotNull Path configsPath, String fileName) {
- return path != null ? Path.of(path) : configsPath.resolve(fileName);
- }
-
- private static Path calculatePath(CommandLine cmd, Option option, @NotNull Path configs, String fileName) {
- return calculatePath(cmd.getOptionValue(option.getLongOpt()), configs, fileName);
+ private static @Nullable Path toPath(@Nullable String path) {
+ return path == null ? null : Path.of(path);
}
- private static Path calculatePath(CommandLine cmd, Option current, Option deprecated, @NotNull Path configs, String fileName) {
- return calculatePath(defaultIfNull(cmd.getOptionValue(current.getLongOpt()), cmd.getOptionValue(deprecated.getLongOpt())), configs, fileName);
- }
}
diff --git a/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt
new file mode 100644
index 000000000..dd192a223
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 Exactpro (Exactpro Systems Limited)
+ *
+ * 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.exactpro.th2.common.microservice
+
+import com.exactpro.th2.common.schema.factory.AbstractCommonFactory
+import java.util.function.BiConsumer
+import java.util.function.Consumer
+
+/**
+ * @param registerResource: register all resources which must be closed in reverse registration order.
+ * @param onPanic: call this method when application can not correct work anymore.
+ */
+class ApplicationContext(
+ val commonFactory: AbstractCommonFactory,
+ val registerResource: BiConsumer,
+ val onPanic: Consumer,
+)
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/microservice/IApplication.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplication.kt
new file mode 100644
index 000000000..2b5a5eb20
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplication.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 Exactpro (Exactpro Systems Limited)
+ *
+ * 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.exactpro.th2.common.microservice
+
+/**
+ * Instance of one time application which works from the [IApplication.start] to [IApplication.close] method.
+ */
+interface IApplication: AutoCloseable {
+ /**
+ * Starts one time application.
+ * This method can be called only once.
+ * @exception IllegalStateException can be thrown when:
+ * * the method is called the two or more time
+ * * close method has been called before
+ * * other reasons
+ */
+ fun start()
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt
new file mode 100644
index 000000000..de53c7ad9
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2023 Exactpro (Exactpro Systems Limited)
+ *
+ * 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.exactpro.th2.common.microservice
+
+import com.exactpro.th2.common.metrics.MetricMonitor
+import com.exactpro.th2.common.metrics.registerLiveness
+import com.exactpro.th2.common.metrics.registerReadiness
+import com.exactpro.th2.common.schema.factory.CommonFactory
+import mu.KotlinLogging
+import java.util.*
+import java.util.concurrent.ConcurrentLinkedDeque
+import java.util.concurrent.locks.Condition
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.thread
+import kotlin.concurrent.withLock
+import kotlin.system.exitProcess
+
+/**
+ * Factory can share closable resources between application. They should be closed on [IApplicationFactory.close] method call
+ */
+interface IApplicationFactory : AutoCloseable {
+ /**
+ * Creates onetime application.
+ * If you need restart of reconfigure application, close old instance and create new one.
+ */
+ fun createApplication(context: ApplicationContext): IApplication
+
+ /**
+ * Close resources shared between created application.
+ */
+ override fun close() {}
+
+ companion object {
+ private val K_LOGGER = KotlinLogging.logger {}
+ private const val APPLICATION_FACTORY_SYSTEM_PROPERTY = "th2.microservice.application-factory"
+ private const val MONITOR_NAME = "microservice_main"
+ @JvmStatic
+ @JvmOverloads
+ fun run(factory: IApplicationFactory? = null, vararg args: String) {
+ val liveness: MetricMonitor = registerLiveness(MONITOR_NAME)
+ val readiness: MetricMonitor = registerReadiness(MONITOR_NAME)
+
+ val resources: Deque = ConcurrentLinkedDeque()
+ val lock = ReentrantLock()
+ val condition: Condition = lock.newCondition()
+
+ configureShutdownHook(resources, lock, condition, liveness, readiness)
+
+ K_LOGGER.info { "Starting a component" }
+ liveness.enable()
+
+ K_LOGGER.info { "Preparing application by ${ factory?.javaClass ?: "internal factory" }" }
+
+ val applicationFactory = factory ?: loadApplicationFactory().also {
+ // add only loaded application factory to resources as internal resource
+ resources.add(Resource("application-factory", it))
+ K_LOGGER.info { "Loaded the ${it.javaClass} factory" }
+ }
+
+ val commonFactory = CommonFactory.createFromArguments(*args)
+ resources.add(Resource("common-factory", commonFactory))
+
+ val application = applicationFactory.createApplication(
+ ApplicationContext(
+ commonFactory,
+ { name: String, closeable: AutoCloseable -> resources.add(Resource(name, closeable)) },
+ ::panic
+ )
+ )
+ resources.add(Resource("application", application))
+
+ K_LOGGER.info { "Starting the ${application.javaClass} application of the '${commonFactory.boxConfiguration.boxName}' component" }
+
+ application.start()
+
+ K_LOGGER.info { "Started the '${commonFactory.boxConfiguration.boxName}' component" }
+ readiness.enable()
+
+ awaitShutdown(lock, condition)
+ }
+
+ private fun panic(ex: Throwable?) {
+ K_LOGGER.error(ex) { "Component panic exception" }
+ exitProcess(2)
+ }
+ private fun configureShutdownHook(
+ resources: Deque,
+ lock: ReentrantLock,
+ condition: Condition,
+ liveness: MetricMonitor,
+ readiness: MetricMonitor,
+ ) {
+ Runtime.getRuntime().addShutdownHook(thread(
+ start = false,
+ name = "Shutdown-hook"
+ ) {
+ K_LOGGER.info { "Shutdown start" }
+ readiness.disable()
+ lock.withLock { condition.signalAll() }
+ resources.descendingIterator().forEachRemaining { resource ->
+ runCatching {
+ resource.closable.close()
+ }.onFailure { e ->
+ K_LOGGER.error(e) { "Cannot close the ${resource.name} resource ${resource.closable::class}" }
+ }
+ }
+ liveness.disable()
+ K_LOGGER.info { "Shutdown end" }
+ })
+ }
+ @Throws(InterruptedException::class)
+ fun awaitShutdown(lock: ReentrantLock, condition: Condition) {
+ lock.withLock {
+ K_LOGGER.info { "Wait shutdown" }
+ condition.await()
+ K_LOGGER.info { "App shutdown" }
+ }
+ }
+
+ private fun loadApplicationFactory(): IApplicationFactory {
+ val instances = ServiceLoader.load(IApplicationFactory::class.java).toList()
+ return when (instances.size) {
+ 0 -> error("No instances of ${IApplicationFactory::class.simpleName}")
+ 1 -> instances.single().also { single ->
+ System.getProperty(APPLICATION_FACTORY_SYSTEM_PROPERTY)?.let { value ->
+ check(value == single::class.qualifiedName) {
+ "Found instance of ${IApplicationFactory::class.simpleName} mismatches the class specified by $APPLICATION_FACTORY_SYSTEM_PROPERTY system property," +
+ "configured: $value, found: ${single::class.qualifiedName}"
+ }
+ }
+ }
+ else -> {
+ System.getProperty(APPLICATION_FACTORY_SYSTEM_PROPERTY)?.let { value ->
+ instances.find { value == it::class.qualifiedName }
+ ?: error(
+ "Found instances of ${IApplicationFactory::class.simpleName} mismatches the class specified by $APPLICATION_FACTORY_SYSTEM_PROPERTY system property," +
+ "configured: $value, found: ${instances.map { Object::class.qualifiedName }}"
+ )
+ } ?: error(
+ "More than 1 instance of ${IApplicationFactory::class.simpleName} has been found " +
+ "and $APPLICATION_FACTORY_SYSTEM_PROPERTY system property isn't specified," +
+ "instances: $instances"
+ )
+ }
+ }
+ }
+
+ private class Resource(val name: String, val closable: AutoCloseable)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/ConfigurationManager.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/ConfigurationManager.kt
index f7eb10d60..3f907d07c 100644
--- a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/ConfigurationManager.kt
+++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/ConfigurationManager.kt
@@ -15,45 +15,36 @@
package com.exactpro.th2.common.schema.configuration
-import com.fasterxml.jackson.databind.ObjectMapper
import mu.KotlinLogging
-import org.apache.commons.text.StringSubstitutor
import java.io.IOException
-import java.nio.file.Files
-import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
-class ConfigurationManager(private val configurationPath: Map, Path>) {
+class ConfigurationManager(
+ private val configurationProvider: IConfigurationProvider,
+ private val configurationPath: Map, String>
+) {
private val configurations: MutableMap, Any?> = ConcurrentHashMap()
- operator fun get(clazz: Class<*>): Path? = configurationPath[clazz]
+ operator fun get(clazz: Class<*>): String? = configurationPath[clazz]
- fun loadConfiguration(
- objectMapper: ObjectMapper,
- stringSubstitutor: StringSubstitutor,
+ fun loadConfiguration(
configClass: Class,
- configPath: Path,
+ configAlias: String,
optional: Boolean
): T {
try {
- if (optional && !(Files.exists(configPath) && Files.size(configPath) > 0)) {
- LOGGER.warn { "Can not read configuration for ${configClass.name}. Use default configuration" }
- return configClass.getDeclaredConstructor().newInstance()
+ return configurationProvider.load(configAlias, configClass) {
+ if (!optional) {
+ throw IllegalStateException("The '$configAlias' is required")
+ }
+ configClass.getDeclaredConstructor().newInstance()
}
-
- val sourceContent = String(Files.readAllBytes(configPath))
- LOGGER.info { "Configuration path $configClass source content $sourceContent" }
- val content: String = stringSubstitutor.replace(sourceContent)
- return objectMapper.readerFor(configClass).readValue(content)
} catch (e: IOException) {
- throw IllegalStateException("Cannot read ${configClass.name} configuration from path '${configPath}'", e)
+ throw IllegalStateException("Cannot read ${configClass.name} configuration for config alias '$configAlias'", e)
}
}
- @Suppress("UNCHECKED_CAST")
- fun getConfigurationOrLoad(
- objectMapper: ObjectMapper,
- stringSubstitutor: StringSubstitutor,
+ fun getConfigurationOrLoad(
configClass: Class,
optional: Boolean
): T {
@@ -61,9 +52,9 @@ class ConfigurationManager(private val configurationPath: Map, Path>) {
checkNotNull(configurationPath[configClass]) {
"Unknown class $configClass"
}.let {
- loadConfiguration(objectMapper, stringSubstitutor, configClass, it, optional)
+ loadConfiguration(configClass, it, optional)
}
- } as T
+ }.let(configClass::cast)
}
companion object {
diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt
new file mode 100644
index 000000000..c6da5c209
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Exactpro (Exactpro Systems Limited)
+ * 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.exactpro.th2.common.schema.configuration
+
+import java.io.InputStream
+import java.util.function.Function
+import java.util.function.Supplier
+
+interface IConfigurationProvider {
+ /**
+ * Loads instance of [configClass] from a resource related to [alias].
+ * Configuration provider parses resource using internal parser.
+ * You can load the same resources using different classes.
+ * @param configClass - target class of loading config
+ * @param alias - alias of related resources
+ * @param default - supplier of default instance of configuration class. Pass `null` if configuration must be loaded
+ */
+ fun load(alias: String, configClass: Class, default: Supplier?): T
+ /**
+ * Loads instance of [configClass] from a resource related to [alias].
+ * Configuration provider parses resource using internal parser.
+ * You can load the same resources using different classes.
+ * @param configClass - target class of loading config
+ * @param alias - alias of related resources
+ */
+ fun load(alias: String, configClass: Class): T = load(alias, configClass, null)
+ /**
+ * Loads instance using [parser] from a resource related to [alias].
+ * You can load the same resources by different classes.
+ * @param alias - alias of related resources
+ * @param parser - function to parse [InputStream] to config instance
+ * @param default - supplier of default instance of configuration class. Pass `null` if configuration must be loaded
+ */
+ fun load(alias: String, parser: Function, default: Supplier?): T
+ /**
+ * Loads instance using [parser] from a resource related to [alias].
+ * You can load the same resources by different classes.
+ * @param alias - alias of related resources
+ * @param parser - function to parse [InputStream] to config instance
+ */
+ fun load(alias: String, parser: Function): T = load(alias, parser, null as Supplier?)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt
new file mode 100644
index 000000000..3121b9c2d
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 Exactpro (Exactpro Systems Limited)
+ * 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.exactpro.th2.common.schema.configuration
+
+import com.exactpro.th2.common.schema.dictionary.DictionaryType
+import java.io.InputStream
+
+interface IDictionaryProvider {
+ fun aliases(): Set
+ fun load(alias: String): InputStream
+ @Deprecated("Load dictionary by type is deprecated, please use load by alias")
+ fun load(type: DictionaryType): InputStream
+ fun load(): InputStream
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/DictionaryProvider.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/DictionaryProvider.kt
new file mode 100644
index 000000000..3ccafdfee
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/DictionaryProvider.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2024 Exactpro (Exactpro Systems Limited)
+ * 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.exactpro.th2.common.schema.configuration.impl
+
+import com.exactpro.th2.common.schema.configuration.IDictionaryProvider
+import com.exactpro.th2.common.schema.configuration.impl.DictionaryKind.ALIAS
+import com.exactpro.th2.common.schema.configuration.impl.DictionaryKind.OLD
+import com.exactpro.th2.common.schema.configuration.impl.DictionaryKind.TYPE
+import com.exactpro.th2.common.schema.dictionary.DictionaryType
+import com.exactpro.th2.common.schema.util.ArchiveUtils
+import org.apache.commons.io.FilenameUtils
+import java.io.ByteArrayInputStream
+import java.io.IOException
+import java.io.InputStream
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.Locale
+import kotlin.io.path.isDirectory
+import kotlin.io.path.isRegularFile
+import kotlin.streams.asSequence
+
+class DictionaryProvider @JvmOverloads constructor(
+ baseDir: Path,
+ paths: Map = emptyMap()
+) : IDictionaryProvider {
+
+ private val directoryPaths = DictionaryKind.createMapping(baseDir)
+ .plus(paths.mapValues { (_, value) -> value.toAbsolutePath() })
+
+ private val dictionaryOldPath: Path
+ get() = requireNotNull(directoryPaths[OLD]) {
+ "$OLD dictionary kind isn't present in $directoryPaths"
+ }
+ private val dictionaryTypePath: Path
+ get() = requireNotNull(directoryPaths[TYPE]) {
+ "$TYPE dictionary kind isn't present in $directoryPaths"
+ }
+
+ private val dictionaryAliasPath: Path
+ get() = requireNotNull(directoryPaths[ALIAS]) {
+ "$ALIAS dictionary kind isn't present in $directoryPaths"
+ }
+ private val directories = directoryPaths.values
+
+ override fun aliases(): Set {
+ try {
+ if (!dictionaryAliasPath.isDirectory()) {
+ return emptySet()
+ }
+
+ val fileList = Files.walk(dictionaryAliasPath, 1).asSequence()
+ .filter(Path::isRegularFile)
+ .toList()
+ val aliasSet: MutableSet = mutableSetOf()
+ val duplicates: MutableMap> = mutableMapOf()
+ for (path in fileList) {
+ val alias = FilenameUtils.removeExtension(path.fileName.toString())
+ .lowercase(Locale.getDefault())
+ if (!aliasSet.add(alias)) {
+ duplicates.getOrPut(alias, ::mutableSetOf).add(path.fileName.toString())
+ }
+ }
+ check(duplicates.isEmpty()) {
+ "Dictionary directory contains files with the same name in different cases, files: $duplicates, path: $dictionaryAliasPath"
+ }
+ return aliasSet
+ } catch (e: IOException) {
+ throw IllegalStateException(
+ "Can not get dictionaries aliases from path: ${dictionaryAliasPath.toAbsolutePath()}",
+ e
+ )
+ }
+ }
+
+ override fun load(alias: String): InputStream {
+ try {
+ require(alias.isNotBlank()) {
+ "Dictionary is blank"
+ }
+
+ check(dictionaryAliasPath.isDirectory()) {
+ "Dictionary dir doesn't exist or isn't directory, path ${dictionaryAliasPath.toAbsolutePath()}"
+ }
+
+ val files = searchInAliasDir(alias)
+ val file = single(listOf(dictionaryAliasPath), files, alias)
+
+ return open(file)
+ } catch (e: IOException) {
+ throw IllegalStateException(
+ "Can not load dictionary by '$alias' alias from path: ${dictionaryAliasPath.toAbsolutePath()}",
+ e
+ )
+ }
+ }
+
+ @Deprecated("Load dictionary by type is deprecated, please use load by alias")
+ override fun load(type: DictionaryType): InputStream {
+ try {
+ var files = searchInAliasDir(type.name)
+
+ if (files.isEmpty()) {
+ files = searchInTypeDir(type)
+ }
+
+ if (files.isEmpty()) {
+ files = searchInOldDir(type.name)
+ }
+
+ val file = single(directories, files, type.name)
+ return open(file)
+ } catch (e: IOException) {
+ throw IllegalStateException("Can not load dictionary by '$type' type from paths: $directories", e)
+ }
+ }
+
+ @Deprecated("Load single dictionary is deprecated, please use load by alias")
+ override fun load(): InputStream {
+ val dirs = listOf(dictionaryAliasPath, dictionaryTypePath)
+ try {
+ var files: List = if (dictionaryAliasPath.isDirectory()) {
+ Files.walk(dictionaryAliasPath, 1).asSequence()
+ .filter(Path::isRegularFile)
+ .toList()
+ } else {
+ emptyList()
+ }
+
+ if (files.isEmpty()) {
+ if (dictionaryTypePath.isDirectory()) {
+ files = Files.walk(dictionaryTypePath, 1).asSequence()
+ .filter(Path::isDirectory)
+ .flatMap { dir ->
+ Files.walk(dir, 1).asSequence()
+ .filter(Path::isRegularFile)
+ }.toList()
+ }
+ }
+
+ check(files.isNotEmpty()) {
+ "No dictionary at path(s): $dirs"
+ }
+ check(files.size == 1) {
+ "Found several dictionaries at paths: $dirs"
+ }
+ val file = files.single()
+ return open(file)
+ } catch (e: IOException) {
+ throw IllegalStateException("Can not read dictionary from from paths: $directories", e)
+ }
+ }
+
+ private fun open(file: Path) = ByteArrayInputStream(
+ ArchiveUtils.getGzipBase64StringDecoder().decode(Files.readString(file))
+ )
+
+ private fun single(dirs: Collection, files: List, alias: String): Path {
+ check(files.isNotEmpty()) {
+ "No dictionary was found by '$alias' name at path(s): $dirs"
+ }
+ check(files.size == 1) {
+ "Found several dictionaries by '$alias' name at path(s): $dirs"
+ }
+ return files.single()
+ }
+
+ private fun searchInOldDir(name: String): List {
+ if (dictionaryOldPath.isDirectory()) {
+ return Files.walk(dictionaryOldPath, 1).asSequence()
+ .filter(Path::isRegularFile)
+ .filter { file -> file.fileName.toString().contains(name) }
+ .toList()
+ }
+ return emptyList()
+ }
+
+ private fun searchInTypeDir(type: DictionaryType): List {
+ val path = type.getDictionary(dictionaryTypePath)
+ if (path.isDirectory()) {
+ return Files.walk(path, 1).asSequence()
+ .filter(Path::isRegularFile)
+ .toList()
+ }
+ return emptyList()
+ }
+
+ private fun searchInAliasDir(alias: String): List {
+ if (dictionaryAliasPath.isDirectory()) {
+ return Files.walk(dictionaryAliasPath, 1).asSequence()
+ .filter(Path::isRegularFile)
+ .filter { file -> alias.equals(toAlias(file), true) }
+ .toList()
+ }
+ return emptyList()
+ }
+
+ companion object {
+ private fun toAlias(path: Path) = FilenameUtils.removeExtension(path.fileName.toString())
+ }
+}
+
+enum class DictionaryKind(
+ val directoryName: String
+) {
+ OLD(""),
+ TYPE("dictionary"),
+ ALIAS("dictionaries");
+
+ companion object {
+ fun createMapping(baseDir: Path): Map {
+ return buildMap {
+ DictionaryKind.values().forEach {
+ put(it, baseDir.resolve(it.directoryName).toAbsolutePath())
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/JsonConfigurationProvider.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/JsonConfigurationProvider.kt
new file mode 100644
index 000000000..110a4c7f1
--- /dev/null
+++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/JsonConfigurationProvider.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2023 Exactpro (Exactpro Systems Limited)
+ * 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.exactpro.th2.common.schema.configuration.impl
+
+import com.exactpro.th2.common.schema.configuration.IConfigurationProvider
+import com.exactpro.th2.common.schema.strategy.route.json.RoutingStrategyModule
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import com.fasterxml.jackson.module.kotlin.KotlinFeature
+import com.fasterxml.jackson.module.kotlin.KotlinModule
+import mu.KotlinLogging
+import org.apache.commons.text.StringSubstitutor
+import java.io.InputStream
+import java.nio.file.Files
+import java.nio.file.Path
+import java.util.concurrent.ConcurrentHashMap
+import java.util.function.Function
+import java.util.function.Supplier
+import kotlin.io.path.exists
+
+
+class JsonConfigurationProvider @JvmOverloads constructor(
+ internal val baseDir: Path,
+ internal val customPaths: Map = emptyMap(),
+) : IConfigurationProvider {
+
+ private val cache = ConcurrentHashMap()
+
+ private fun getPathFor(alias: String): Path = customPaths[alias] ?: baseDir.resolve("${alias}.${EXTENSION}")
+
+ override fun load(alias: String, configClass: Class, default: Supplier?): T =
+ requireNotNull(cache.compute(alias) { _, value ->
+ when {
+ value == null -> loadFromFile(alias, default) { MAPPER.readValue(it, configClass) }
+ !configClass.isInstance(value) -> loadFromFile(alias, default) {
+ MAPPER.readValue(it, configClass)
+ }.also {
+ K_LOGGER.info { "Parsed value for '$alias' alias has been updated from: ${value::class.java} to ${it::class.java}" }
+ }
+ else -> value
+ }
+ }).also { value ->
+ check(configClass.isInstance(value)) {
+ "Stored configuration instance of $alias config alias mismatches, " +
+ "expected: ${configClass.canonicalName}, actual: ${value::class.java.canonicalName}"
+ }
+ }.let(configClass::cast)
+
+ override fun load(alias: String, parser: Function, default: Supplier?): T =
+ loadFromFile(alias, default, parser).apply {
+ cache.put(alias, this)?.let {
+ K_LOGGER.info { "Parsed value for '$alias' alias has been updated from: ${this@JsonConfigurationProvider::class.java} to ${it::class.java}" }
+ }
+ }
+
+ private fun loadFromFile(alias: String, default: Supplier?, parser: Function): T {
+ val configPath = this.getPathFor(alias)
+ if (!configPath.exists()) {
+ K_LOGGER.warn { "'$configPath' file related to the '$alias' config alias doesn't exist" }
+ return default?.get()
+ ?: error("Configuration loading failure, '$configPath' file related to the '$alias' config alias doesn't exist")
+ }
+ if (Files.size(configPath) == 0L) {
+ K_LOGGER.warn { "'$configPath' file related to the '$alias' config alias has 0 size" }
+ return default?.get()
+ ?: error("Configuration loading failure, '$configPath' file related to the '$alias' config alias has 0 size")
+ }
+
+ val sourceContent = String(Files.readAllBytes(configPath))
+ K_LOGGER.info { "'$configPath' file related to the '$alias' config alias has source content $sourceContent" }
+ val content = SUBSTITUTOR.get().replace(sourceContent)
+ return requireNotNull(parser.apply(content.byteInputStream())) {
+ "Parsed format of config content can't be null, alias: '$alias'"
+ }
+ }
+
+ companion object {
+ private const val EXTENSION = "json"
+
+ private val K_LOGGER = KotlinLogging.logger {}
+ private val SUBSTITUTOR: ThreadLocal = object : ThreadLocal() {
+ override fun initialValue(): StringSubstitutor = StringSubstitutor(System.getenv())
+ }
+
+ @JvmField
+ val MAPPER = ObjectMapper().apply {
+ registerModules(
+ KotlinModule.Builder()
+ .withReflectionCacheSize(512)
+ .configure(KotlinFeature.NullToEmptyCollection, false)
+ .configure(KotlinFeature.NullToEmptyMap, false)
+ .configure(KotlinFeature.NullIsSameAsDefault, false)
+ .configure(KotlinFeature.SingletonSupport, true)
+ .configure(KotlinFeature.StrictNullChecks, false)
+ .build(),
+ RoutingStrategyModule(this),
+ JavaTimeModule()
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/exactpro/th2/common/schema/factory/FactorySettings.kt b/src/main/kotlin/com/exactpro/th2/common/schema/factory/FactorySettings.kt
index 47c0c6566..d1eeee354 100644
--- a/src/main/kotlin/com/exactpro/th2/common/schema/factory/FactorySettings.kt
+++ b/src/main/kotlin/com/exactpro/th2/common/schema/factory/FactorySettings.kt
@@ -31,11 +31,17 @@ import com.exactpro.th2.common.schema.message.impl.rabbitmq.raw.RabbitRawBatchRo
import java.nio.file.Path
data class FactorySettings @JvmOverloads constructor(
+ @Deprecated("Will be removed in future releases")
var messageRouterParsedBatchClass: Class> = RabbitParsedBatchRouter::class.java,
+ @Deprecated("Will be removed in future releases")
var messageRouterRawBatchClass: Class> = RabbitRawBatchRouter::class.java,
+ @Deprecated("Will be removed in future releases")
var messageRouterMessageGroupBatchClass: Class> = RabbitMessageGroupBatchRouter::class.java,
+ @Deprecated("Will be removed in future releases")
var eventBatchRouterClass: Class> = EventBatchRouter::class.java,
+ @Deprecated("Will be removed in future releases")
var grpcRouterClass: Class = DefaultGrpcRouter::class.java,
+ @Deprecated("Will be removed in future releases")
var notificationEventBatchRouterClass: Class> = NotificationEventBatchRouter::class.java,
var rabbitMQ: Path? = null,
var routerMQ: Path? = null,
@@ -47,9 +53,12 @@ data class FactorySettings @JvmOverloads constructor(
var prometheus: Path? = null,
var boxConfiguration: Path? = null,
var custom: Path? = null,
- @Deprecated("Will be removed in future releases") var dictionaryTypesDir: Path? = null,
+ var baseConfigDir: Path? = null,
+ @Deprecated("Will be removed in future releases")
+ var dictionaryTypesDir: Path? = null,
var dictionaryAliasesDir: Path? = null,
- @Deprecated("Will be removed in future releases") var oldDictionariesDir: Path? = null,
+ @Deprecated("Will be removed in future releases")
+ var oldDictionariesDir: Path? = null,
var variables: MutableMap = HashMap()
) {
fun messageRouterParsedBatchClass(messageRouterParsedBatchClass: Class>): FactorySettings {
@@ -143,7 +152,7 @@ data class FactorySettings @JvmOverloads constructor(
}
fun oldDictionariesDir(oldDictionariesDir: Path?): FactorySettings {
- this.dictionaryTypesDir = oldDictionariesDir
+ this.oldDictionariesDir = oldDictionariesDir
return this
}
diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/DictionaryLoadTest.kt b/src/test/kotlin/com/exactpro/th2/common/schema/DictionaryLoadTest.kt
new file mode 100644
index 000000000..d8c0b9bca
--- /dev/null
+++ b/src/test/kotlin/com/exactpro/th2/common/schema/DictionaryLoadTest.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2020-2022 Exactpro (Exactpro Systems Limited)
+ * 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.exactpro.th2.common.schema
+
+import com.exactpro.th2.common.schema.dictionary.DictionaryType
+import com.exactpro.th2.common.schema.factory.CommonFactory
+import com.exactpro.th2.common.schema.util.ArchiveUtils
+import org.apache.commons.lang3.RandomStringUtils
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
+import java.nio.file.Path
+import kotlin.io.path.absolutePathString
+import kotlin.io.path.createDirectories
+import kotlin.io.path.writeBytes
+import kotlin.io.path.writeText
+import kotlin.test.assertEquals
+
+@Suppress("DEPRECATION")
+class DictionaryLoadTest {
+
+ @TempDir
+ lateinit var tempDir: Path
+
+ @BeforeEach
+ fun beforeEach() {
+ writePrometheus(tempDir)
+ }
+
+ //--//--//--readDictionary()--//--//--//
+
+ @Test
+ fun `test read dictionary from old dictionary dir`() {
+ val content = writeDictionary(tempDir.resolve(Path.of("MAIN")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(content, commonFactory.readDictionary().use { String(it.readAllBytes()) })
+ }
+ }
+
+ @Test
+ fun `test read dictionary from old dictionary dir - file name mismatch`() {
+ writeDictionary(tempDir.resolve(Path.of("main")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.readDictionary()
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["MAIN", "main", "test-dictionary"])
+ fun `test read dictionary from type dictionary dir`(fileName: String) {
+ val content = writeDictionary(tempDir.resolve(Path.of("dictionary", "main", fileName)))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(content, commonFactory.readDictionary().use { String(it.readAllBytes()) })
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["MAIN", "main", "test-dictionary"])
+ fun `test read dictionary from type dictionary dir - dictionary name mismatch`(
+ fileName: String,
+
+ ) {
+ writeDictionary(tempDir.resolve(Path.of("dictionary", "MAIN", fileName)))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.readDictionary()
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["MAIN", "main", "MAIN.xml", "main.json"])
+ fun `test read dictionary from alias dictionary dir`(fileName: String) {
+ val content = writeDictionary(tempDir.resolve(Path.of("dictionaries", fileName)))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(content, commonFactory.readDictionary().use { String(it.readAllBytes()) })
+ }
+ }
+
+ //--//--//--readDictionary()--//--//--//
+
+ @Test
+ fun `test read dictionary by type from old dictionary dir`() {
+ val content = writeDictionary(tempDir.resolve(Path.of("INCOMING")))
+ writeDictionary(tempDir.resolve(Path.of("MAIN")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(
+ content,
+ commonFactory.readDictionary(DictionaryType.INCOMING).use { String(it.readAllBytes()) })
+ }
+ }
+
+ @Test
+ fun `test read dictionary by type from old dictionary dir - file name mismatch`() {
+ writeDictionary(tempDir.resolve(Path.of("incoming")))
+ writeDictionary(tempDir.resolve(Path.of("MAIN")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.readDictionary(DictionaryType.INCOMING)
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["INCOMING", "incoming", "test-dictionary"])
+ fun `test read dictionary by type from type dictionary dir`(fileName: String) {
+ val content = writeDictionary(tempDir.resolve(Path.of("dictionary", "incoming", fileName)))
+ writeDictionary(tempDir.resolve(Path.of("dictionary", "main", "MAIN")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(
+ content,
+ commonFactory.readDictionary(DictionaryType.INCOMING).use { String(it.readAllBytes()) })
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["INCOMING", "incoming", "test-dictionary"])
+ fun `test read dictionary by type from type dictionary dir - dictionary name mismatch`(
+ fileName: String,
+
+ ) {
+ writeDictionary(tempDir.resolve(Path.of("dictionary", "INCOMING", fileName)))
+ writeDictionary(tempDir.resolve(Path.of("dictionary", "main", "MAIN")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.readDictionary(DictionaryType.INCOMING)
+ }
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["INCOMING", "incoming", "INCOMING.xml", "incoming.json"])
+ fun `test read dictionary by type from alias dictionary dir`(fileName: String) {
+ val content = writeDictionary(tempDir.resolve(Path.of("dictionaries", fileName)))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "MAIN")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(
+ content,
+ commonFactory.readDictionary(DictionaryType.INCOMING).use { String(it.readAllBytes()) })
+ }
+ }
+
+ //--//--//--loadSingleDictionary()--//--//--//
+
+ @Test
+ fun `test load single dictionary from alias dictionary dir`() {
+ val content = writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(content, commonFactory.loadSingleDictionary().use { String(it.readAllBytes()) })
+ }
+ }
+
+ @Test
+ fun `test load single dictionary from alias dictionary dir - several dictionaries`() {
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary-1")))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary-2")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.loadSingleDictionary()
+ }
+ }
+ }
+
+ @Test
+ fun `test load single dictionary from alias dictionary dir - several dictionaries with name in different case`() {
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary")))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "TEST-DICTIONARY")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.loadSingleDictionary()
+ }
+ }
+ }
+
+ //--//--//--loadDictionary()--//--//--//
+
+ @ParameterizedTest
+ @ValueSource(strings = ["TEST-ALIAS", "test-alias"])
+ fun `test load dictionary by alias from alias dictionary dir`(fileName: String) {
+ val content = writeDictionary(tempDir.resolve(Path.of("dictionaries", fileName)))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(content, commonFactory.loadDictionary("test-alias").use { String(it.readAllBytes()) })
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = ["TEST-DICTIONARY", "test-dictionary"])
+ fun `test load dictionary by alias from alias dictionary dir - several dictionaries with name in different case`(
+ alias: String,
+
+ ) {
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary")))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "TEST-DICTIONARY")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.loadDictionary(alias)
+ }
+ }
+ }
+
+ //--//--//--loadAliases--//--//--//
+
+ @Test
+ fun `test dictionary aliases from alias dictionary dir`() {
+ val alias1 = "test-dictionary-1"
+ val alias2 = "TEST-DICTIONARY-2"
+ val file1 = "$alias1.xml"
+ val file2 = "$alias2.json"
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", file1)))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", file2)))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(setOf(alias1, alias2.lowercase()), commonFactory.dictionaryAliases)
+ }
+ }
+
+ @Test
+ fun `test dictionary aliases from alias dictionary dir - empty directory`() {
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ assertEquals(emptySet(), commonFactory.dictionaryAliases)
+ }
+ }
+
+ @Test
+ fun `test dictionary aliases from alias dictionary dir - several dictionaries with name in different case`() {
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "test-dictionary")))
+ writeDictionary(tempDir.resolve(Path.of("dictionaries", "TEST-DICTIONARY")))
+ CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory ->
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ commonFactory.dictionaryAliases
+ }
+ }
+ }
+
+ //--//--//--Others--//--//--//
+
+ private fun writePrometheus(cfgPath: Path) {
+ cfgPath.resolve("prometheus.json").writeText("{\"enabled\":false}")
+ }
+
+ private fun writeDictionary(path: Path): String {
+ val content = RandomStringUtils.randomAlphanumeric(10)
+ path.parent.createDirectories()
+ path.writeBytes(ArchiveUtils.getGzipBase64StringEncoder().encode(content))
+ return content
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt b/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt
deleted file mode 100644
index 76fae29fa..000000000
--- a/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2020-2022 Exactpro (Exactpro Systems Limited)
- * 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.exactpro.th2.common.schema
-
-import com.exactpro.th2.common.schema.dictionary.DictionaryType
-import com.exactpro.th2.common.schema.factory.CommonFactory
-import com.exactpro.th2.common.schema.factory.FactorySettings
-import org.junit.jupiter.api.Assertions
-import org.junit.jupiter.api.Test
-import java.nio.file.Path
-
-class TestDictionaryLoad {
-
- @Test
- fun `test file load dictionary`() {
- val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries")
-
- factory.readDictionary().use {
- assert(String(it.readAllBytes()) == "test file")
- }
- }
-
- @Test
- fun `test folder load dictionary`() {
- val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries")
-
- factory.readDictionary(DictionaryType.LEVEL1).use {
- assert(String(it.readAllBytes()) == "test file")
- }
- }
-
- @Test
- fun `test folder load dictionaries by alias`() {
- val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries")
-
- Assertions.assertDoesNotThrow {
- factory.loadDictionary("test_alias_2").use {
- assert(String(it.readAllBytes()) == "test file")
- }
- }
- }
-
- @Test
- fun `test folder load all dictionary aliases`() {
- val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries")
- val expectedNames = listOf("main", "test_alias_1", "test_alias_2", "test_alias_3", "test_alias_4")
- val names = factory.dictionaryAliases
- Assertions.assertEquals(5, names.size)
- Assertions.assertTrue(names.containsAll(expectedNames))
- }
-
- @Test
- fun `test folder load single dictionary from folder with several`() {
- val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries")
-
- Assertions.assertThrows(IllegalStateException::class.java) {
- factory.loadSingleDictionary()
- }
- }
-
- @Test
- fun `test folder load single dictionary`() {
- val customSettings = FactorySettings().apply {
- prometheus = Path.of("src/test/resources/test_load_dictionaries/prometheus.json")
- dictionaryAliasesDir = Path.of("src/test/resources/test_load_dictionaries/single_dictionary")
- }
- val customFactory = CommonFactory(customSettings)
-
- customFactory.loadSingleDictionary().use {
- assert(String(it.readAllBytes()) == "test file")
- }
- }
-
- @Test
- fun `test folder load single dictionary by type as alias`() {
- val customSettings = FactorySettings().apply {
- prometheus = Path.of("src/test/resources/test_load_dictionaries/prometheus.json")
- dictionaryTypesDir = Path.of("..")
- dictionaryAliasesDir = Path.of("src/test/resources/test_load_dictionaries/dictionaries")
- }
- val customFactory = CommonFactory(customSettings)
-
- customFactory.readDictionary().use {
- assert(String(it.readAllBytes()) == "test file")
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/factory/CommonFactoryTest.kt b/src/test/kotlin/com/exactpro/th2/common/schema/factory/CommonFactoryTest.kt
index c918f8879..9aecfd34f 100644
--- a/src/test/kotlin/com/exactpro/th2/common/schema/factory/CommonFactoryTest.kt
+++ b/src/test/kotlin/com/exactpro/th2/common/schema/factory/CommonFactoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2023 Exactpro (Exactpro Systems Limited)
+ * Copyright 2023-2024 Exactpro (Exactpro Systems Limited)
* 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
@@ -14,110 +14,559 @@
*/
package com.exactpro.th2.common.schema.factory
-import com.exactpro.cradle.cassandra.CassandraStorageSettings
import com.exactpro.th2.common.metrics.PrometheusConfiguration
import com.exactpro.th2.common.schema.box.configuration.BoxConfiguration
+import com.exactpro.th2.common.schema.configuration.impl.JsonConfigurationProvider
import com.exactpro.th2.common.schema.cradle.CradleConfidentialConfiguration
import com.exactpro.th2.common.schema.cradle.CradleNonConfidentialConfiguration
+import com.exactpro.th2.common.schema.dictionary.DictionaryType
+import com.exactpro.th2.common.schema.dictionary.DictionaryType.INCOMING
+import com.exactpro.th2.common.schema.dictionary.DictionaryType.MAIN
+import com.exactpro.th2.common.schema.dictionary.DictionaryType.OUTGOING
import com.exactpro.th2.common.schema.factory.CommonFactory.CONFIG_DEFAULT_PATH
-import com.exactpro.th2.common.schema.factory.CommonFactory.CUSTOM_FILE_NAME
-import com.exactpro.th2.common.schema.factory.CommonFactory.DICTIONARY_ALIAS_DIR_NAME
-import com.exactpro.th2.common.schema.factory.CommonFactory.DICTIONARY_TYPE_DIR_NAME
import com.exactpro.th2.common.schema.factory.CommonFactory.TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY
+import com.exactpro.th2.common.schema.factory.CommonFactoryTest.Companion.DictionaryHelper.ALIAS
+import com.exactpro.th2.common.schema.factory.CommonFactoryTest.Companion.DictionaryHelper.Companion.assertDictionary
+import com.exactpro.th2.common.schema.factory.CommonFactoryTest.Companion.DictionaryHelper.OLD
+import com.exactpro.th2.common.schema.factory.CommonFactoryTest.Companion.DictionaryHelper.TYPE
import com.exactpro.th2.common.schema.grpc.configuration.GrpcConfiguration
import com.exactpro.th2.common.schema.grpc.configuration.GrpcRouterConfiguration
import com.exactpro.th2.common.schema.message.configuration.MessageRouterConfiguration
import com.exactpro.th2.common.schema.message.impl.rabbitmq.configuration.ConnectionManagerConfiguration
import com.exactpro.th2.common.schema.message.impl.rabbitmq.configuration.RabbitMQConfiguration
+import com.exactpro.th2.common.schema.util.ArchiveUtils
+import org.apache.commons.io.file.PathUtils.deleteDirectory
+import org.apache.commons.lang3.RandomStringUtils
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.samePropertyValuesAs
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertAll
+import org.junit.jupiter.api.assertThrows
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.EnumSource
+import org.junitpioneer.jupiter.ClearSystemProperty
import org.junitpioneer.jupiter.SetSystemProperty
import java.nio.file.Path
+import java.util.Locale
+import kotlin.io.path.absolutePathString
+import kotlin.io.path.createDirectories
+import kotlin.io.path.exists
+import kotlin.io.path.writeBytes
+import kotlin.reflect.cast
+import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
+@Suppress("DEPRECATION", "removal")
class CommonFactoryTest {
- @Test
- fun `test load config by default path (default constructor)`() {
- CommonFactory().use { commonFactory ->
- assertConfigs(commonFactory, CONFIG_DEFAULT_PATH)
+ @BeforeEach
+ fun beforeEach() {
+ if (TEMP_DIR.toPath().exists()) {
+ deleteDirectory(TEMP_DIR.toPath())
}
+ CMD_ARG_CFG_DIR_PATH.createDirectories()
+ SYSTEM_PROPERTY_CFG_DIR_PATH.createDirectories()
+ CUSTOM_DIR_PATH.createDirectories()
}
- @Test
- fun `test load config by default path (createFromArguments(empty))`() {
- CommonFactory.createFromArguments().use { commonFactory ->
- assertConfigs(commonFactory, CONFIG_DEFAULT_PATH)
+ @Nested
+ @ClearSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY)
+ inner class CreateByDefaultConstructor {
+ @Test
+ fun `test default`() {
+ CommonFactory().use { commonFactory ->
+ assertThrowsDictionaryDir(commonFactory)
+ assertThrowsConfigs(commonFactory)
+ }
}
- }
- @Test
- fun `test load config by custom path (createFromArguments(not empty))`() {
- CommonFactory.createFromArguments("-c", CONFIG_DIR_IN_RESOURCE).use { commonFactory ->
- assertConfigs(commonFactory, Path.of(CONFIG_DIR_IN_RESOURCE))
+ @Test
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ fun `test with system property`() {
+ CommonFactory().use { commonFactory ->
+ assertDictionaryDirs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+ assertConfigs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+ }
}
}
- @Test
- @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = CONFIG_DIR_IN_RESOURCE)
- fun `test load config by environment variable path (default constructor)`() {
- CommonFactory().use { commonFactory ->
- assertConfigs(commonFactory, Path.of(CONFIG_DIR_IN_RESOURCE))
+ @Nested
+ @ClearSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY)
+ inner class CreateBySettingsConstructor {
+ @Test
+ fun `test default`() {
+ CommonFactory(FactorySettings()).use { commonFactory ->
+ assertThrowsDictionaryDir(commonFactory)
+ assertThrowsConfigs(commonFactory)
+ }
}
- }
- @Test
- @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = CONFIG_DIR_IN_RESOURCE)
- fun `test load config by environment variable path (createFromArguments(empty))`() {
- CommonFactory.createFromArguments().use { commonFactory ->
- assertConfigs(commonFactory, Path.of(CONFIG_DIR_IN_RESOURCE))
+ @ParameterizedTest
+ @EnumSource(value = DictionaryHelper::class)
+ fun `test custom dictionary path`(helper: DictionaryHelper) {
+ CommonFactory(FactorySettings().apply {
+ helper.setOption(this, CUSTOM_DIR_PATH)
+ }).use { commonFactory ->
+ val content = helper.writeDictionaryByCustomPath(CUSTOM_DIR_PATH, DICTIONARY_NAME)
+ assertDictionary(commonFactory, content)
+
+ assertThrowsConfigs(commonFactory)
+ }
}
- }
- @Test
- @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = CONFIG_DIR_IN_RESOURCE)
- fun `test load config by custom path (createFromArguments(not empty) + environment variable)`() {
- CommonFactory.createFromArguments("-c", CONFIG_DIR_IN_RESOURCE).use { commonFactory ->
- assertConfigs(commonFactory, Path.of(CONFIG_DIR_IN_RESOURCE))
+ @ParameterizedTest
+ @EnumSource(ConfigHelper::class)
+ fun `test custom path for config`(helper: ConfigHelper) {
+ val configPath = CUSTOM_DIR_PATH.resolve(helper.alias)
+ CommonFactory(FactorySettings().apply {
+ helper.setOption(this, configPath)
+ }).use { commonFactory ->
+ assertThrowsDictionaryDir(commonFactory)
+
+ val cfgBean = helper.writeConfigByCustomPath(configPath)
+ assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean))
+ }
+ }
+
+ @Test
+ fun `test with custom config path`() {
+ CommonFactory(FactorySettings().apply {
+ baseConfigDir = CMD_ARG_CFG_DIR_PATH
+ }).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = DictionaryHelper::class)
+ fun `test with custom config path and custom dictionary path`(helper: DictionaryHelper) {
+ val settings = FactorySettings().apply {
+ baseConfigDir = CMD_ARG_CFG_DIR_PATH
+ helper.setOption(this, CMD_ARG_CFG_DIR_PATH)
+ }
+ CommonFactory(settings).use { commonFactory ->
+ val content = helper.writeDictionaryByCustomPath(CMD_ARG_CFG_DIR_PATH, DICTIONARY_NAME)
+ assertDictionary(commonFactory, content)
+
+ assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ }
+ }
+
+ @ParameterizedTest
+ @EnumSource(ConfigHelper::class)
+ fun `test with custom config path and custom path for config`(helper: ConfigHelper) {
+ val configPath = CUSTOM_DIR_PATH.resolve(helper.alias)
+ CommonFactory(FactorySettings().apply {
+ baseConfigDir = CMD_ARG_CFG_DIR_PATH
+ helper.setOption(this, configPath)
+ }).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+
+ val cfgBean = helper.writeConfigByCustomPath(configPath)
+ assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean))
+ }
+ }
+
+ @ParameterizedTest
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ @EnumSource(DictionaryHelper::class)
+ fun `test with system property`(helper: DictionaryHelper) {
+ CommonFactory(FactorySettings()).use { commonFactory ->
+ val content = helper.writeDictionaryByDefaultPath(SYSTEM_PROPERTY_CFG_DIR_PATH, DICTIONARY_NAME)
+ assertDictionary(commonFactory, content)
+
+ assertConfigs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+ }
+ }
+
+ @ParameterizedTest
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ @EnumSource(value = DictionaryHelper::class)
+ fun `test with system property and custom dictionary path`(helper: DictionaryHelper) {
+ val factorySettings = FactorySettings().apply {
+ helper.setOption(this, CUSTOM_DIR_PATH)
+ }
+ CommonFactory(factorySettings).use { commonFactory ->
+ val content = helper.writeDictionaryByCustomPath(CUSTOM_DIR_PATH, DICTIONARY_NAME)
+ assertDictionary(commonFactory, content)
+
+ assertConfigs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+ }
+ }
+
+ @ParameterizedTest
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ @EnumSource(ConfigHelper::class)
+ fun `test with system property and custom path for config`(helper: ConfigHelper) {
+ val configPath = CUSTOM_DIR_PATH.resolve(helper.alias)
+ val factorySettings = FactorySettings().apply {
+ helper.setOption(this, configPath)
+ }
+ CommonFactory(factorySettings).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+
+ val cfgBean = helper.writeConfigByCustomPath(configPath)
+ assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean))
+ }
+ }
+
+ @Test
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ fun `test with cmd config argument and system property`() {
+ CommonFactory(FactorySettings().apply {
+ baseConfigDir = CMD_ARG_CFG_DIR_PATH
+ }).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ }
}
- }
+ @ParameterizedTest
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ @EnumSource(value = DictionaryHelper::class)
+ fun `test with cmd config argument and system property and custom dictionary path`(helper: DictionaryHelper) {
+ val settings = FactorySettings().apply {
+ baseConfigDir = CMD_ARG_CFG_DIR_PATH
+ helper.setOption(this, CUSTOM_DIR_PATH)
+ }
+ CommonFactory(settings).use { commonFactory ->
+ val content = helper.writeDictionaryByCustomPath(CUSTOM_DIR_PATH, DICTIONARY_NAME)
+ assertDictionary(commonFactory, content)
- private fun assertConfigs(commonFactory: CommonFactory, configPath: Path) {
- CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.forEach { (configName, actualPathSupplier) ->
- assertEquals(configPath.resolve(configName), commonFactory.actualPathSupplier(), "Configured config path: $configPath, config name: $configName")
+ assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ }
+ }
+
+ @ParameterizedTest
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ @EnumSource(ConfigHelper::class)
+ fun `test with cmd config argument and system property and custom path for config`(helper: ConfigHelper) {
+ val configPath = CUSTOM_DIR_PATH.resolve(helper.alias)
+ CommonFactory(FactorySettings().apply {
+ baseConfigDir = CMD_ARG_CFG_DIR_PATH
+ helper.setOption(this, configPath)
+ }).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+
+ val cfgBean = helper.writeConfigByCustomPath(configPath)
+ assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean))
+ }
}
- assertConfigurationManager(commonFactory, configPath)
}
- private fun assertConfigurationManager(commonFactory: CommonFactory, configPath: Path) {
- CONFIG_CLASSES.forEach { clazz ->
- assertNotNull(commonFactory.configurationManager[clazz])
- assertEquals(configPath, commonFactory.configurationManager[clazz]?.parent , "Configured config path: $configPath, config class: $clazz")
+ @Nested
+ @ClearSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY)
+ inner class CreateFromArguments {
+ @Test
+ fun `test without parameters`() {
+ CommonFactory.createFromArguments().use { commonFactory ->
+ assertThrowsDictionaryDir(commonFactory)
+ assertThrowsConfigs(commonFactory)
+ }
+ }
+
+ @Test
+ fun `test with cmd config argument`() {
+ CommonFactory.createFromArguments("-c", CMD_ARG_CFG_DIR).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ }
+ }
+
+ @Test
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ fun `test with system property`() {
+ CommonFactory.createFromArguments().use { commonFactory ->
+ assertDictionaryDirs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+ assertConfigs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH)
+ }
+ }
+
+ @Test
+ @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR)
+ fun `test with cmd config argument and system property`() {
+ CommonFactory.createFromArguments("-c", CMD_ARG_CFG_DIR).use { commonFactory ->
+ assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH)
+ }
}
}
companion object {
- private const val CONFIG_DIR_IN_RESOURCE = "src/test/resources/test_common_factory_load_configs"
-
- private val CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER: Map Path> = mapOf(
- CUSTOM_FILE_NAME to { pathToCustomConfiguration },
- DICTIONARY_ALIAS_DIR_NAME to { pathToDictionaryAliasesDir },
- DICTIONARY_TYPE_DIR_NAME to { pathToDictionaryTypesDir },
- )
-
- private val CONFIG_CLASSES: Set> = setOf(
- RabbitMQConfiguration::class.java,
- MessageRouterConfiguration::class.java,
- ConnectionManagerConfiguration::class.java,
- GrpcConfiguration::class.java,
- GrpcRouterConfiguration::class.java,
- CradleConfidentialConfiguration::class.java,
- CradleNonConfidentialConfiguration::class.java,
- CassandraStorageSettings::class.java,
- PrometheusConfiguration::class.java,
- BoxConfiguration::class.java,
- )
+ private const val TEMP_DIR = "build/tmp/test/common-factory"
+ private const val CMD_ARG_CFG_DIR = "$TEMP_DIR/test-cmd-arg-config"
+ private const val SYSTEM_PROPERTY_CFG_DIR = "$TEMP_DIR/test-system-property-config"
+ private const val CUSTOM_DIR = "$TEMP_DIR/test-custom-dictionary-path"
+
+ private val CMD_ARG_CFG_DIR_PATH = CMD_ARG_CFG_DIR.toPath()
+ private val SYSTEM_PROPERTY_CFG_DIR_PATH = SYSTEM_PROPERTY_CFG_DIR.toPath()
+ private val CUSTOM_DIR_PATH = CUSTOM_DIR.toPath()
+
+ private val DICTIONARY_NAME = MAIN.name
+
+ private fun String.toPath() = Path.of(this)
+
+ // FIXME: find another way to check default dictionary path
+ private fun assertThrowsDictionaryDir(commonFactory: CommonFactory) {
+ val exception = assertThrows("Search dictionary by default path") {
+ commonFactory.readDictionary()
+ }
+ val message = assertNotNull(exception.message, "Exception message is null")
+ assertAll(
+ {
+ assertContains(
+ message,
+ other = CONFIG_DEFAULT_PATH.resolve("dictionaries").absolutePathString(),
+ message = "Exception message contains alias dictionaries path"
+ )
+ },
+ {
+ assertContains(
+ message,
+ other = CONFIG_DEFAULT_PATH.resolve("dictionary").absolutePathString(),
+ message = "Exception message contains type dictionaries path"
+ )
+ },
+ {
+ assertContains(
+ message,
+ other = CONFIG_DEFAULT_PATH.absolutePathString(),
+ message = "Exception message contains old dictionaries path"
+ )
+ }
+ )
+ }
+
+ private fun assertDictionaryDirs(commonFactory: CommonFactory, basePath: Path) {
+ assertAll(
+ {
+ val content = ALIAS.writeDictionaryByDefaultPath(basePath, MAIN.name)
+ ALIAS.assertDictionary(commonFactory, MAIN, content)
+ },
+ {
+ val content = TYPE.writeDictionaryByDefaultPath(basePath, INCOMING.name)
+ TYPE.assertDictionary(commonFactory, INCOMING, content)
+ },
+ {
+ val content = OLD.writeDictionaryByDefaultPath(basePath, OUTGOING.name)
+ OLD.assertDictionary(commonFactory, OUTGOING, content)
+ }
+ )
+ }
+
+ // FIXME: find another way to check default config path
+ private fun assertThrowsConfigs(commonFactory: CommonFactory) {
+ assertAll(
+ *ConfigHelper.values().asSequence()
+ .filter { it != ConfigHelper.PROMETHEUS_CFG_ALIAS && it != ConfigHelper.BOX_CFG_ALIAS }
+ .map { helper ->
+ {
+ val exception = assertThrows(
+ "Search config $helper by default path"
+ ) {
+ helper.loadConfig(commonFactory)
+ }
+ val message = assertNotNull(exception.message, "Exception message is null")
+ assertContains(message, other = CONFIG_DEFAULT_PATH.resolve("${helper.alias}.json").absolutePathString(), message = "Exception message contains alias config path")
+ }
+ }.toList().toTypedArray()
+ )
+
+ val provider = assertProvider(commonFactory)
+ assertEquals(CONFIG_DEFAULT_PATH, provider.baseDir)
+ assertEquals(0, provider.customPaths.size)
+ }
+
+ private fun assertConfigs(commonFactory: CommonFactory, configPath: Path) {
+ assertAll(
+ ConfigHelper.values().map { helper ->
+ {
+ val cfgBean = helper.writeConfigByDefaultPath(configPath)
+ assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean))
+ }
+ }
+ )
+ }
+
+ private fun assertProvider(commonFactory: CommonFactory): JsonConfigurationProvider {
+ assertEquals(JsonConfigurationProvider::class, commonFactory.configurationProvider::class)
+ return JsonConfigurationProvider::class.cast(commonFactory.getConfigurationProvider())
+ }
+
+ data class TestCustomConfig(val testField: String = "test-value")
+
+ enum class ConfigHelper(
+ val alias: String,
+ private val configClass: Class<*>
+ ) {
+ RABBIT_MQ_CFG_ALIAS("rabbitMQ", RabbitMQConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.rabbitMQ = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): RabbitMQConfiguration = RabbitMQConfiguration(
+ "test-host",
+ "test-vHost",
+ 1234,
+ "test-username",
+ "test-password",
+ ).also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ ROUTER_MQ_CFG_ALIAS("mq", MessageRouterConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.routerMQ = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): MessageRouterConfiguration = MessageRouterConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ CONNECTION_MANAGER_CFG_ALIAS("mq_router", ConnectionManagerConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.connectionManagerSettings = filePath
+ }
+ override fun writeConfigByCustomPath(filePath: Path): ConnectionManagerConfiguration = ConnectionManagerConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ GRPC_CFG_ALIAS("grpc", GrpcConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.grpc = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): GrpcConfiguration = GrpcConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ ROUTER_GRPC_CFG_ALIAS("grpc_router", GrpcRouterConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.routerGRPC = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): GrpcRouterConfiguration = GrpcRouterConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ CRADLE_CONFIDENTIAL_CFG_ALIAS("cradle", CradleConfidentialConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.cradleConfidential = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): CradleConfidentialConfiguration = CradleConfidentialConfiguration(
+ "test-dataCenter",
+ "test-host",
+ "test-keyspace",
+ ).also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ CRADLE_NON_CONFIDENTIAL_CFG_ALIAS("cradle_manager", CradleNonConfidentialConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.cradleNonConfidential = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): CradleNonConfidentialConfiguration = CradleNonConfidentialConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ PROMETHEUS_CFG_ALIAS("prometheus", PrometheusConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.prometheus = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): PrometheusConfiguration = PrometheusConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ BOX_CFG_ALIAS("box", BoxConfiguration::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.boxConfiguration = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): BoxConfiguration = BoxConfiguration()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ },
+ CUSTOM_CFG_ALIAS("custom", TestCustomConfig::class.java) {
+ override fun setOption(settings: FactorySettings, filePath: Path) {
+ settings.custom = filePath
+ }
+
+ override fun writeConfigByCustomPath(filePath: Path): TestCustomConfig = TestCustomConfig()
+ .also { CommonFactory.MAPPER.writeValue(filePath.toFile(), it) }
+ };
+
+ abstract fun setOption(settings: FactorySettings, filePath: Path)
+
+ abstract fun writeConfigByCustomPath(filePath: Path): Any
+
+ open fun writeConfigByDefaultPath(configPath: Path): Any = writeConfigByCustomPath(configPath.resolve("${alias}.json"))
+
+ fun loadConfig(factory: CommonFactory): Any =
+ factory.getConfigurationProvider().load(alias, configClass)
+ }
+
+ enum class DictionaryHelper {
+ ALIAS {
+ override fun setOption(settings: FactorySettings, path: Path) {
+ settings.dictionaryAliasesDir = path
+ }
+
+ override fun writeDictionaryByDefaultPath(basePath: Path, fileName: String): String {
+ return DictionaryHelper.writeDictionary(basePath.resolve("dictionaries").resolve(fileName))
+ }
+
+ override fun writeDictionaryByCustomPath(basePath: Path, fileName: String): String {
+ return DictionaryHelper.writeDictionary(basePath.resolve(fileName))
+ }
+ },
+ TYPE {
+ override fun setOption(settings: FactorySettings, path: Path) {
+ settings.dictionaryTypesDir = path
+ }
+
+ override fun writeDictionaryByDefaultPath(basePath: Path, fileName: String): String {
+ return DictionaryHelper.writeDictionary(basePath.resolve("dictionary").resolve(fileName.lowercase(
+ Locale.getDefault()
+ )).resolve(fileName))
+ }
+
+ override fun writeDictionaryByCustomPath(basePath: Path, fileName: String): String {
+ return DictionaryHelper.writeDictionary(basePath.resolve(fileName.lowercase(
+ Locale.getDefault()
+ )).resolve(fileName))
+ }
+ },
+ OLD {
+ override fun setOption(settings: FactorySettings, path: Path) {
+ settings.oldDictionariesDir = path
+ }
+
+ override fun writeDictionaryByDefaultPath(basePath: Path, fileName: String): String {
+ return DictionaryHelper.writeDictionary(basePath.resolve(fileName))
+ }
+
+ override fun writeDictionaryByCustomPath(basePath: Path, fileName: String): String {
+ return DictionaryHelper.writeDictionary(basePath.resolve(fileName))
+ }
+ };
+
+ abstract fun setOption(settings: FactorySettings, path: Path)
+ abstract fun writeDictionaryByDefaultPath(basePath: Path, fileName: String): String
+ abstract fun writeDictionaryByCustomPath(basePath: Path, fileName: String): String
+
+ fun assertDictionary(factory: CommonFactory, type: DictionaryType, content: String) {
+ assertEquals(content, factory.readDictionary(type).use { String(it.readAllBytes()) })
+ }
+
+ companion object {
+ fun assertDictionary(factory: CommonFactory, content: String) {
+ assertEquals(content, factory.readDictionary().use { String(it.readAllBytes()) })
+ }
+
+ private fun writeDictionary(path: Path): String {
+ val content = RandomStringUtils.randomAlphanumeric(10)
+ path.parent.createDirectories()
+ path.writeBytes(ArchiveUtils.getGzipBase64StringEncoder().encode(content))
+ return content
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/test/resources/test_common_factory_load_configs/custom.json b/src/test/resources/test_cmd_arg_config/custom.json
similarity index 100%
rename from src/test/resources/test_common_factory_load_configs/custom.json
rename to src/test/resources/test_cmd_arg_config/custom.json
diff --git a/src/test/resources/test_load_dictionaries/dictionaries/main b/src/test/resources/test_load_dictionaries/dictionaries/main
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/dictionaries/main
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_1.encoded b/src/test/resources/test_load_dictionaries/dictionaries/test_alias_1.encoded
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_1.encoded
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_2 b/src/test/resources/test_load_dictionaries/dictionaries/test_alias_2
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_2
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_3.encoded b/src/test/resources/test_load_dictionaries/dictionaries/test_alias_3.encoded
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_3.encoded
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_4 b/src/test/resources/test_load_dictionaries/dictionaries/test_alias_4
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/dictionaries/test_alias_4
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/level1/test_dictionary.encoded b/src/test/resources/test_load_dictionaries/level1/test_dictionary.encoded
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/level1/test_dictionary.encoded
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/prometheus.json b/src/test/resources/test_load_dictionaries/prometheus.json
deleted file mode 100644
index c994fef1c..000000000
--- a/src/test/resources/test_load_dictionaries/prometheus.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "enabled": false
-}
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/single_dictionary/test_alias_1.encoded b/src/test/resources/test_load_dictionaries/single_dictionary/test_alias_1.encoded
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/single_dictionary/test_alias_1.encoded
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/test_dictionary_LEVEL1.encoded b/src/test/resources/test_load_dictionaries/test_dictionary_LEVEL1.encoded
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/test_dictionary_LEVEL1.encoded
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_load_dictionaries/test_dictionary_MAIN.encoded b/src/test/resources/test_load_dictionaries/test_dictionary_MAIN.encoded
deleted file mode 100644
index 0f3ff0936..000000000
--- a/src/test/resources/test_load_dictionaries/test_dictionary_MAIN.encoded
+++ /dev/null
@@ -1 +0,0 @@
-H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA=
\ No newline at end of file
diff --git a/src/test/resources/test_system_property_config/custom.json b/src/test/resources/test_system_property_config/custom.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/src/test/resources/test_system_property_config/custom.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file