From 665af827598766f3e7ab88f21074805c60ac5e57 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 6 Oct 2023 20:34:00 +0400 Subject: [PATCH 01/10] Added microservice entry-point --- README.md | 6 +- gradle.properties | 2 +- .../common/microservice/ApplicationContext.kt | 28 ++++ .../th2/common/microservice/IApplication.kt | 31 ++++ .../microservice/IApplicationFactory.kt | 32 ++++ .../exactpro/th2/common/microservice/Main.kt | 151 ++++++++++++++++++ 6 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt create mode 100644 src/main/kotlin/com/exactpro/th2/common/microservice/IApplication.kt create mode 100644 src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt create mode 100644 src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt diff --git a/README.md b/README.md index 07f0a973..16d37cb6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# th2 common library (Java) (5.4.2) +# th2 common library (Java) (5.6.0) ## Usage @@ -491,6 +491,10 @@ dependencies { ## Release notes +### 5.6.0-dev +#### Feature: ++ Added common microservice entry point + ### 5.5.0-dev #### Changed: diff --git a/gradle.properties b/gradle.properties index 2eae5e59..5962a2f7 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.5.0 +release_version=5.6.0 description='th2 common library (Java)' vcs_url=https://github.com/th2-net/th2-common-j kapt.include.compile.classpath=false 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 00000000..edf9df50 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * @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: (AutoCloseable) -> Unit, + val onPanic: (Throwable?) -> Unit, +) \ 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 00000000..2b5a5eb2 --- /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 00000000..1d97e1fe --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt @@ -0,0 +1,32 @@ +/* + * 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 + +/** + * 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() {} +} \ No newline at end of file diff --git a/src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt new file mode 100644 index 00000000..cb419177 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt @@ -0,0 +1,151 @@ +/* + * 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.Deque +import java.util.ServiceLoader +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 + +private val K_LOGGER = KotlinLogging.logger {} + +class Main(args: Array) { + + private val liveness: MetricMonitor = registerLiveness(MONITOR_NAME) + private val readiness: MetricMonitor = registerReadiness(MONITOR_NAME) + + private val resources: Deque = ConcurrentLinkedDeque() + private val lock = ReentrantLock() + private val condition: Condition = lock.newCondition() + + private val commonFactory: CommonFactory + private val application: IApplication + + init { + configureShutdownHook(resources, lock, condition, liveness, readiness) + + K_LOGGER.info { "Starting a component" } + liveness.enable() + + val applicationFactory = loadApplicationFactory() + .also(resources::add) + commonFactory = CommonFactory.createFromArguments(*args) + .also(resources::add) + application = applicationFactory.createApplication(ApplicationContext(commonFactory, resources::add, ::panic)) + .also(resources::add) + + K_LOGGER.info { "Prepared the '${commonFactory.boxConfiguration.boxName}' component" } + } + + fun run() { + K_LOGGER.info { "Starting the '${commonFactory.boxConfiguration.boxName}' component" } + + application.start() + + K_LOGGER.info { "Started the '${commonFactory.boxConfiguration.boxName}' component" } + readiness.enable() + + awaitShutdown(lock, condition) + } + + companion object { + private const val APPLICATION_FACTORY_SYSTEM_PROPERTY = "th2.microservice.application-factory" + private const val MONITOR_NAME = "microservice_main" + 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" + ) + } + } + } + } +} + +fun main(args: Array) { + try { + Main(args).run() + } catch (ex: Exception) { + K_LOGGER.error(ex) { "Cannot start the box" } + exitProcess(1) + } +} +fun panic(ex: Throwable?) { + K_LOGGER.error(ex) { "Component panic exception" } + exitProcess(2) +} +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.close() + }.onFailure { e -> + K_LOGGER.error(e) { "Cannot close resource ${resource::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" } + } +} \ No newline at end of file From c2b86df467c2eef56098914ad1eacdb3e7256943 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Sat, 7 Oct 2023 00:29:59 +0400 Subject: [PATCH 02/10] Added configuration provider --- README.md | 1 + .../schema/factory/AbstractCommonFactory.java | 23 +---- .../configuration/IConfigurationProvider.kt | 20 +++++ .../impl/JsonConfigurationProvider.kt | 90 +++++++++++++++++++ 4 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt create mode 100644 src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/JsonConfigurationProvider.kt diff --git a/README.md b/README.md index 16d37cb6..240334c4 100644 --- a/README.md +++ b/README.md @@ -494,6 +494,7 @@ dependencies { ### 5.6.0-dev #### Feature: + Added common microservice entry point ++ Added configuration provider to common factory ### 5.5.0-dev 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 4cc7e554..3b92c28a 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 @@ -32,6 +32,7 @@ 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; @@ -56,12 +57,8 @@ import com.exactpro.th2.common.schema.message.impl.rabbitmq.custom.RabbitCustomRouter; 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; @@ -108,23 +105,7 @@ 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() - ); - } - + public static final ObjectMapper MAPPER = JsonConfigurationProvider.MAPPER; private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCommonFactory.class); private final StringSubstitutor stringSubstitutor; 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 00000000..0978c9d2 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt @@ -0,0 +1,20 @@ +/* + * 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 + +interface IConfigurationProvider { + fun load(configClass: Class, alias: String, default: () -> T): T +} \ 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 00000000..f06109ab --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/JsonConfigurationProvider.kt @@ -0,0 +1,90 @@ +/* + * 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.nio.file.Files +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import kotlin.io.path.exists +import kotlin.io.path.isDirectory + +class JsonConfigurationProvider( + private val baseDir: Path +) : IConfigurationProvider { + + init { + require(baseDir.isDirectory()) { + "The '$baseDir' base directory doesn't exist or isn't directory" + } + } + + private val cache = ConcurrentHashMap() + + @Suppress("UNCHECKED_CAST") + override fun load(configClass: Class, alias: String, default: () -> T): T = + cache.computeIfAbsent(alias) { + val configPath = baseDir.resolve("${alias}.${EXTENSION}") + if (!configPath.exists()) { + K_LOGGER.debug { "'$configPath' file related to the '$alias' config alias doesn't exist" } + return@computeIfAbsent default() + } + + 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) + requireNotNull(MAPPER.readValue(content, configClass)) { + "Parsed format of the '$alias' config alias content can't be null" + } + }.also { value -> + check(configClass.isInstance(value)) { + "Stored configuration instance of $alias config alias mismatches, " + + "expected: ${configClass.canonicalName}, actual: ${value::class.java.canonicalName}" + } + } as T + + 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, false) + .configure(KotlinFeature.StrictNullChecks, false) + .build(), + RoutingStrategyModule(this), + JavaTimeModule() + ) + } + } +} \ No newline at end of file From b2261f9fb24a2b5f15b145539b7a4c0bf5f789e1 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 9 Oct 2023 11:15:25 +0400 Subject: [PATCH 03/10] Refactored --- .../schema/factory/AbstractCommonFactory.java | 6 +- .../common/schema/factory/CommonFactory.java | 122 ++++++++------ .../common/microservice/ApplicationContext.kt | 6 +- .../microservice/IApplicationFactory.kt | 130 +++++++++++++++ .../exactpro/th2/common/microservice/Main.kt | 151 ------------------ .../configuration/ConfigurationManager.kt | 37 ++--- .../impl/JsonConfigurationProvider.kt | 51 ++++-- .../schema/factory/CommonFactoryTest.kt | 37 +++-- 8 files changed, 288 insertions(+), 252 deletions(-) delete mode 100644 src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt 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 3b92c28a..80e0bf94 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 @@ -382,7 +382,7 @@ public MessageRouter getCustomMessageRouter(Class messageClass) { * @throws IllegalStateException if can not read configuration */ public T getConfiguration(Path configPath, Class configClass, ObjectMapper customObjectMapper) { - return getConfigurationManager().loadConfiguration(customObjectMapper, stringSubstitutor, configClass, configPath, false); + return getConfigurationManager().loadConfiguration(configClass, configPath, false); } /** @@ -393,7 +393,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(stringSubstitutor, configClass, optional); } public RabbitMQConfiguration getRabbitMqConfiguration() { @@ -409,7 +409,7 @@ public MessageRouterConfiguration getMessageRouterConfiguration() { } public GrpcConfiguration getGrpcConfiguration() { - return getConfigurationManager().getConfigurationOrLoad(MAPPER, stringSubstitutor, GrpcConfiguration.class, false); + return getConfigurationManager().getConfigurationOrLoad(stringSubstitutor, GrpcConfiguration.class, false); } public GrpcRouterConfiguration getGrpcRouterConfiguration() { 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 8fe4353c..411d5c9e 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 @@ -19,6 +19,8 @@ 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.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; @@ -83,16 +85,16 @@ 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 CUSTOM_CFG_ALIAS = "custom"; + 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 @@ -117,17 +119,19 @@ public class CommonFactory extends AbstractCommonFactory { private final Path dictionaryTypesDir; private final Path dictionaryAliasesDir; private final Path oldDictionariesDir; + final IConfigurationProvider configurationProvider; 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); + custom = defaultPathIfNull(settings.getCustom(), CUSTOM_CFG_ALIAS); 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); + configurationProvider = createConfigurationProvider(settings); + configurationManager = createConfigurationManager(configurationProvider, settings); start(); } @@ -159,6 +163,11 @@ protected Path getOldPathToDictionariesDir() { protected ConfigurationManager getConfigurationManager() { return configurationManager; } + + IConfigurationProvider getConfigurationProvider() { + return configurationProvider; + } + /** * Create {@link CommonFactory} from command line arguments * @@ -271,16 +280,16 @@ public static CommonFactory createFromArguments(String... args) { 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.setRabbitMQ(calculatePath(cmd, rabbitConfigurationOption, configs, RABBIT_MQ_CFG_ALIAS)); + settings.setRouterMQ(calculatePath(cmd, messageRouterConfigurationOption, configs, ROUTER_MQ_CFG_ALIAS)); + settings.setConnectionManagerSettings(calculatePath(cmd, connectionManagerConfigurationOption, configs, CONNECTION_MANAGER_CFG_ALIAS)); + settings.setGrpc(calculatePath(cmd, grpcConfigurationOption, grpcRouterConfigurationOption, configs, GRPC_CFG_ALIAS)); + settings.setRouterGRPC(calculatePath(cmd, grpcRouterConfigOption, configs, ROUTER_GRPC_CFG_ALIAS)); + settings.setCradleConfidential(calculatePath(cmd, cradleConfidentialConfigurationOption, cradleConfigurationOption, configs, CRADLE_CONFIDENTIAL_CFG_ALIAS)); + settings.setCradleNonConfidential(calculatePath(cmd, cradleManagerConfigurationOption, configs, CRADLE_NON_CONFIDENTIAL_CFG_ALIAS)); + settings.setPrometheus(calculatePath(cmd, prometheusConfigurationOption, configs, PROMETHEUS_CFG_ALIAS)); + settings.setBoxConfiguration(calculatePath(cmd, boxConfigurationOption, configs, BOX_CFG_ALIAS)); + settings.setCustom(calculatePath(cmd, customConfigurationOption, configs, CUSTOM_CFG_ALIAS)); 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()); @@ -331,7 +340,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 +407,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); @@ -649,25 +658,46 @@ 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 IConfigurationProvider createConfigurationProvider(FactorySettings settings) { + Map paths = new HashMap<>(); + 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(getConfigPath(), paths); + } + private static ConfigurationManager createConfigurationManager(IConfigurationProvider configurationProvider, FactorySettings settings) { + 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 Path defaultPathIfNull(Path path, String name) { return path == null ? getConfigPath().resolve(name) : path; } + private static void putIfNotNull(@NotNull Map paths, @NotNull String alias, @Nullable Path path) { + requireNonNull(paths, "'Paths' can't be null"); + requireNonNull(paths, "'Alias' can't be null"); + if (path != null) { + paths.put(alias, path); + } + } + private static void writeFile(Path path, String data) throws IOException { try (OutputStream outputStream = Files.newOutputStream(path)) { IOUtils.write(data, outputStream, Charsets.UTF_8); diff --git a/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt index edf9df50..dd192a22 100644 --- a/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt +++ b/src/main/kotlin/com/exactpro/th2/common/microservice/ApplicationContext.kt @@ -16,6 +16,8 @@ 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. @@ -23,6 +25,6 @@ import com.exactpro.th2.common.schema.factory.AbstractCommonFactory */ class ApplicationContext( val commonFactory: AbstractCommonFactory, - val registerResource: (AutoCloseable) -> Unit, - val onPanic: (Throwable?) -> Unit, + val registerResource: BiConsumer, + val onPanic: Consumer, ) \ 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 index 1d97e1fe..7ad0473c 100644 --- a/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt @@ -15,6 +15,19 @@ */ 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 */ @@ -29,4 +42,121 @@ interface IApplicationFactory : AutoCloseable { * 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 + fun run(factory: IApplicationFactory?, 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/microservice/Main.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt deleted file mode 100644 index cb419177..00000000 --- a/src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.Deque -import java.util.ServiceLoader -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 - -private val K_LOGGER = KotlinLogging.logger {} - -class Main(args: Array) { - - private val liveness: MetricMonitor = registerLiveness(MONITOR_NAME) - private val readiness: MetricMonitor = registerReadiness(MONITOR_NAME) - - private val resources: Deque = ConcurrentLinkedDeque() - private val lock = ReentrantLock() - private val condition: Condition = lock.newCondition() - - private val commonFactory: CommonFactory - private val application: IApplication - - init { - configureShutdownHook(resources, lock, condition, liveness, readiness) - - K_LOGGER.info { "Starting a component" } - liveness.enable() - - val applicationFactory = loadApplicationFactory() - .also(resources::add) - commonFactory = CommonFactory.createFromArguments(*args) - .also(resources::add) - application = applicationFactory.createApplication(ApplicationContext(commonFactory, resources::add, ::panic)) - .also(resources::add) - - K_LOGGER.info { "Prepared the '${commonFactory.boxConfiguration.boxName}' component" } - } - - fun run() { - K_LOGGER.info { "Starting the '${commonFactory.boxConfiguration.boxName}' component" } - - application.start() - - K_LOGGER.info { "Started the '${commonFactory.boxConfiguration.boxName}' component" } - readiness.enable() - - awaitShutdown(lock, condition) - } - - companion object { - private const val APPLICATION_FACTORY_SYSTEM_PROPERTY = "th2.microservice.application-factory" - private const val MONITOR_NAME = "microservice_main" - 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" - ) - } - } - } - } -} - -fun main(args: Array) { - try { - Main(args).run() - } catch (ex: Exception) { - K_LOGGER.error(ex) { "Cannot start the box" } - exitProcess(1) - } -} -fun panic(ex: Throwable?) { - K_LOGGER.error(ex) { "Component panic exception" } - exitProcess(2) -} -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.close() - }.onFailure { e -> - K_LOGGER.error(e) { "Cannot close resource ${resource::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" } - } -} \ 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 f7eb10d6..ed9ab6e2 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,38 @@ 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(configClass, configAlias) { + if (optional) { + configClass.getDeclaredConstructor().newInstance() + } + throw IllegalStateException("The '$configAlias' is required") } - - 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 StringSubstitutor.getConfigurationOrLoad( configClass: Class, optional: Boolean ): T { @@ -61,7 +54,7 @@ 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 } 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 index f06109ab..6621db35 100644 --- 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 @@ -28,41 +28,64 @@ import java.nio.file.Path import java.util.concurrent.ConcurrentHashMap import kotlin.io.path.exists import kotlin.io.path.isDirectory +import kotlin.io.path.isRegularFile class JsonConfigurationProvider( - private val baseDir: Path + private val baseDir: Path, + private val customPaths: Map = emptyMap(), ) : IConfigurationProvider { init { require(baseDir.isDirectory()) { "The '$baseDir' base directory doesn't exist or isn't directory" } + customPaths.forEach { (alias, path) -> + require(path.isRegularFile()) { + "The '$path' path to the '$alias' alias doesn't exist or isn't regular file" + } + } } private val cache = ConcurrentHashMap() + operator fun get(alias: String): Path = customPaths[alias] ?: baseDir.resolve("${alias}.${EXTENSION}") + @Suppress("UNCHECKED_CAST") override fun load(configClass: Class, alias: String, default: () -> T): T = - cache.computeIfAbsent(alias) { - val configPath = baseDir.resolve("${alias}.${EXTENSION}") - if (!configPath.exists()) { - K_LOGGER.debug { "'$configPath' file related to the '$alias' config alias doesn't exist" } - return@computeIfAbsent default() + requireNotNull(cache.compute(alias) { _, value -> + when { + value == null -> loadFromFile(configClass, alias, default) + !configClass.isInstance(value) -> loadFromFile(configClass, alias, default).also { + K_LOGGER.info { "Parsed value for '$alias' alias has been updated from: ${value::class.java} to ${it::class.java}" } + } + else -> value } - - 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) - requireNotNull(MAPPER.readValue(content, configClass)) { - "Parsed format of the '$alias' config alias content can't be null" - } - }.also { value -> + }).also { value -> check(configClass.isInstance(value)) { "Stored configuration instance of $alias config alias mismatches, " + "expected: ${configClass.canonicalName}, actual: ${value::class.java.canonicalName}" } } as T + private fun loadFromFile(configClass: Class, alias: String, default: () -> T): T { + val configPath = this[alias] + if (!configPath.exists()) { + K_LOGGER.debug { "'$configPath' file related to the '$alias' config alias doesn't exist" } + return default() + } + if (Files.size(configPath) == 0L) { + K_LOGGER.warn { "'$configPath' file related to the '$alias' config alias has 0 size" } + return default() + } + + 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(MAPPER.readValue(content, configClass)) { + "Parsed format of the '$alias' config alias content can't be null" + } + } + companion object { private const val EXTENSION = "json" 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 c918f887..bc4c1d7b 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 @@ -19,10 +19,19 @@ import com.exactpro.th2.common.metrics.PrometheusConfiguration import com.exactpro.th2.common.schema.box.configuration.BoxConfiguration import com.exactpro.th2.common.schema.cradle.CradleConfidentialConfiguration import com.exactpro.th2.common.schema.cradle.CradleNonConfidentialConfiguration +import com.exactpro.th2.common.schema.factory.CommonFactory.BOX_CFG_ALIAS 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.CONNECTION_MANAGER_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.CRADLE_CONFIDENTIAL_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.CRADLE_NON_CONFIDENTIAL_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.CUSTOM_CFG_ALIAS 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.GRPC_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.PROMETHEUS_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.RABBIT_MQ_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.ROUTER_GRPC_CFG_ALIAS +import com.exactpro.th2.common.schema.factory.CommonFactory.ROUTER_MQ_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY import com.exactpro.th2.common.schema.grpc.configuration.GrpcConfiguration import com.exactpro.th2.common.schema.grpc.configuration.GrpcRouterConfiguration @@ -92,7 +101,7 @@ class CommonFactoryTest { } private fun assertConfigurationManager(commonFactory: CommonFactory, configPath: Path) { - CONFIG_CLASSES.forEach { clazz -> + CONFIG_CLASSES.forEach { (clazz, alias) -> assertNotNull(commonFactory.configurationManager[clazz]) assertEquals(configPath, commonFactory.configurationManager[clazz]?.parent , "Configured config path: $configPath, config class: $clazz") } @@ -102,22 +111,22 @@ class CommonFactoryTest { 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 }, + CUSTOM_CFG_ALIAS 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 val CONFIG_CLASSES: Map, String> = mapOf( + RabbitMQConfiguration::class.java to RABBIT_MQ_CFG_ALIAS, + MessageRouterConfiguration::class.java to ROUTER_MQ_CFG_ALIAS, + ConnectionManagerConfiguration::class.java to CONNECTION_MANAGER_CFG_ALIAS, + GrpcConfiguration::class.java to GRPC_CFG_ALIAS, + GrpcRouterConfiguration::class.java to ROUTER_GRPC_CFG_ALIAS, + CradleConfidentialConfiguration::class.java to CRADLE_CONFIDENTIAL_CFG_ALIAS, + CradleNonConfidentialConfiguration::class.java to CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, + CassandraStorageSettings::class.java to CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, + PrometheusConfiguration::class.java to PROMETHEUS_CFG_ALIAS, + BoxConfiguration::class.java to BOX_CFG_ALIAS, ) } } \ No newline at end of file From 07a3537ba16281c431c63f1068a9ae2a7e99eb29 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Thu, 26 Oct 2023 13:18:02 +0400 Subject: [PATCH 04/10] Refactored CommonFactory constructors --- .../schema/factory/AbstractCommonFactory.java | 33 +- .../common/schema/factory/CommonFactory.java | 110 +++-- .../configuration/ConfigurationManager.kt | 14 +- .../configuration/IConfigurationProvider.kt | 37 +- .../impl/JsonConfigurationProvider.kt | 54 +-- .../common/schema/factory/FactorySettings.kt | 15 +- .../schema/factory/CommonFactoryTest.kt | 428 +++++++++++++++--- .../custom.json | 0 .../test_system_property_config/custom.json | 1 + 9 files changed, 501 insertions(+), 191 deletions(-) rename src/test/resources/{test_common_factory_load_configs => test_cmd_arg_config}/custom.json (100%) create mode 100644 src/test/resources/test_system_property_config/custom.json 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 80e0bf94..f3ab2a7a 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 @@ -62,12 +62,10 @@ 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.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; @@ -88,7 +86,6 @@ import static com.exactpro.cradle.cassandra.CassandraStorageSettings.DEFAULT_MAX_UNCOMPRESSED_TEST_EVENT_SIZE; import static com.exactpro.cradle.cassandra.CassandraStorageSettings.DEFAULT_RESULT_PAGE_SIZE; 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} @@ -105,9 +102,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"; + 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> messageRouterParsedBatchClass; private final Class> messageRouterRawBatchClass; @@ -146,7 +143,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() { @@ -378,11 +374,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(configClass, configPath, false); + public T getConfiguration(String configAlias, Class configClass, ObjectMapper customObjectMapper) { + return getConfigurationManager().loadConfiguration(configClass, configAlias, false); } /** @@ -393,7 +389,7 @@ public T getConfiguration(Path configPath, Class configClass, ObjectMappe * @return configuration object */ protected T getConfigurationOrLoad(Class configClass, boolean optional) { - return getConfigurationManager().getConfigurationOrLoad(stringSubstitutor, configClass, optional); + return getConfigurationManager().getConfigurationOrLoad(configClass, optional); } public RabbitMQConfiguration getRabbitMqConfiguration() { @@ -409,7 +405,7 @@ public MessageRouterConfiguration getMessageRouterConfiguration() { } public GrpcConfiguration getGrpcConfiguration() { - return getConfigurationManager().getConfigurationOrLoad(stringSubstitutor, GrpcConfiguration.class, false); + return getConfigurationManager().getConfigurationOrLoad(GrpcConfiguration.class, false); } public GrpcRouterConfiguration getGrpcRouterConfiguration() { @@ -499,17 +495,7 @@ public CradleManager getCradleManager() { * @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); } /** @@ -599,11 +585,6 @@ public EventID getRootEventId() { protected abstract ConfigurationManager getConfigurationManager(); - /** - * @return Path to custom configuration - */ - protected abstract Path getPathToCustomConfiguration(); - /** * @return Path to dictionaries with type dir */ 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 411d5c9e..e5849ab6 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 @@ -73,7 +73,6 @@ 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; /** @@ -91,7 +90,6 @@ public class CommonFactory extends AbstractCommonFactory { 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 CUSTOM_CFG_ALIAS = "custom"; 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"; @@ -115,7 +113,6 @@ 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; @@ -126,12 +123,12 @@ public class CommonFactory extends AbstractCommonFactory { public CommonFactory(FactorySettings settings) { super(settings); - custom = defaultPathIfNull(settings.getCustom(), CUSTOM_CFG_ALIAS); - dictionaryTypesDir = defaultPathIfNull(settings.getDictionaryTypesDir(), DICTIONARY_TYPE_DIR_NAME); - dictionaryAliasesDir = defaultPathIfNull(settings.getDictionaryAliasesDir(), DICTIONARY_ALIAS_DIR_NAME); - oldDictionariesDir = requireNonNullElseGet(settings.getOldDictionariesDir(), CommonFactory::getConfigPath); + dictionaryTypesDir = defaultPathIfNull(settings.getDictionaryTypesDir(), settings.getBaseConfigDir(), DICTIONARY_TYPE_DIR_NAME); + dictionaryAliasesDir = defaultPathIfNull(settings.getDictionaryAliasesDir(), settings.getBaseConfigDir(), DICTIONARY_ALIAS_DIR_NAME); + oldDictionariesDir = defaultPathIfNull(settings.getOldDictionariesDir(), settings.getBaseConfigDir(), ""); + configurationProvider = createConfigurationProvider(settings); - configurationManager = createConfigurationManager(configurationProvider, settings); + configurationManager = createConfigurationManager(configurationProvider); start(); } @@ -139,11 +136,6 @@ public CommonFactory() { this(new FactorySettings()); } - @Override - protected Path getPathToCustomConfiguration() { - return custom; - } - @Override protected Path getPathToDictionaryTypesDir() { return dictionaryTypesDir; @@ -245,7 +237,7 @@ public static CommonFactory createFromArguments(String... args) { try { CommandLine cmd = new DefaultParser().parse(options, args); - Path configs = getConfigPath(cmd.getOptionValue(configOption.getLongOpt())); + Path configs = calculateCmdPath(cmd.getOptionValue(configOption.getLongOpt())); if (cmd.hasOption(namespaceOption.getLongOpt()) && cmd.hasOption(boxNameOption.getLongOpt())) { String namespace = cmd.getOptionValue(namespaceOption.getLongOpt()); @@ -276,24 +268,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_CFG_ALIAS)); - settings.setRouterMQ(calculatePath(cmd, messageRouterConfigurationOption, configs, ROUTER_MQ_CFG_ALIAS)); - settings.setConnectionManagerSettings(calculatePath(cmd, connectionManagerConfigurationOption, configs, CONNECTION_MANAGER_CFG_ALIAS)); - settings.setGrpc(calculatePath(cmd, grpcConfigurationOption, grpcRouterConfigurationOption, configs, GRPC_CFG_ALIAS)); - settings.setRouterGRPC(calculatePath(cmd, grpcRouterConfigOption, configs, ROUTER_GRPC_CFG_ALIAS)); - settings.setCradleConfidential(calculatePath(cmd, cradleConfidentialConfigurationOption, cradleConfigurationOption, configs, CRADLE_CONFIDENTIAL_CFG_ALIAS)); - settings.setCradleNonConfidential(calculatePath(cmd, cradleManagerConfigurationOption, configs, CRADLE_NON_CONFIDENTIAL_CFG_ALIAS)); - settings.setPrometheus(calculatePath(cmd, prometheusConfigurationOption, configs, PROMETHEUS_CFG_ALIAS)); - settings.setBoxConfiguration(calculatePath(cmd, boxConfigurationOption, configs, BOX_CFG_ALIAS)); - settings.setCustom(calculatePath(cmd, customConfigurationOption, configs, CUSTOM_CFG_ALIAS)); - 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(calculateCmdPath(cmd.getOptionValue(rabbitConfigurationOption.getLongOpt()))); + settings.setRouterMQ(calculateCmdPath(cmd.getOptionValue(messageRouterConfigurationOption.getLongOpt()))); + settings.setConnectionManagerSettings(calculateCmdPath(cmd.getOptionValue(connectionManagerConfigurationOption.getLongOpt()))); + settings.setGrpc(calculateCmdPath(defaultIfNull(cmd.getOptionValue(grpcConfigurationOption.getLongOpt()), cmd.getOptionValue(grpcRouterConfigurationOption.getLongOpt())))); + settings.setRouterGRPC(calculateCmdPath(cmd.getOptionValue(grpcRouterConfigOption.getLongOpt()))); + settings.setCradleConfidential(calculateCmdPath(cmd.getOptionValue(cradleConfidentialConfigurationOption.getLongOpt()))); + settings.setCradleNonConfidential(calculateCmdPath(defaultIfNull(cmd.getOptionValue(cradleManagerConfigurationOption.getLongOpt()), cmd.getOptionValue(cradleConfigurationOption.getLongOpt())))); + settings.setPrometheus(calculateCmdPath(cmd.getOptionValue(prometheusConfigurationOption.getLongOpt()))); + settings.setBoxConfiguration(calculateCmdPath(cmd.getOptionValue(boxConfigurationOption.getLongOpt()))); + settings.setCustom(calculateCmdPath(cmd.getOptionValue(customConfigurationOption.getLongOpt()))); + settings.setDictionaryTypesDir(calculateCmdPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt()))); + settings.setDictionaryAliasesDir(calculateCmdPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt()))); + settings.setOldDictionariesDir(calculateCmdPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt()))); return new CommonFactory(settings); } catch (ParseException e) { @@ -560,6 +554,25 @@ public InputStream readDictionary(DictionaryType dictionaryType) { } static @NotNull Path getConfigPath() { + return getConfigPath(null); + } + + /** + * 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; + } + 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"); + } + String pathString = System.getProperty(TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY); if (pathString != null) { Path path = Paths.get(pathString); @@ -579,21 +592,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)); @@ -660,6 +658,7 @@ private static void createDirectory(Path dir) throws IOException { 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()); @@ -669,9 +668,9 @@ private static IConfigurationProvider createConfigurationProvider(FactorySetting 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(getConfigPath(), paths); + return new JsonConfigurationProvider(defaultIfNull(settings.getBaseConfigDir(), getConfigPath()), paths); } - private static ConfigurationManager createConfigurationManager(IConfigurationProvider configurationProvider, FactorySettings settings) { + 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); @@ -686,8 +685,8 @@ private static ConfigurationManager createConfigurationManager(IConfigurationPro return new ConfigurationManager(configurationProvider, paths); } - private static Path defaultPathIfNull(Path path, String name) { - return path == null ? getConfigPath().resolve(name) : path; + private static Path defaultPathIfNull(Path customPath, Path basePath, String name) { + return customPath == null ? getConfigPath(basePath).resolve(name) : customPath; } private static void putIfNotNull(@NotNull Map paths, @NotNull String alias, @Nullable Path path) { @@ -717,15 +716,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 @Nullable Path calculateCmdPath(@Nullable String path) { + return path == null ? null : Path.of(path); } - private static Path calculatePath(CommandLine cmd, Option option, @NotNull Path configs, String fileName) { - return calculatePath(cmd.getOptionValue(option.getLongOpt()), configs, fileName); - } - - 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/schema/configuration/ConfigurationManager.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/ConfigurationManager.kt index ed9ab6e2..3f907d07 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 @@ -16,7 +16,6 @@ package com.exactpro.th2.common.schema.configuration import mu.KotlinLogging -import org.apache.commons.text.StringSubstitutor import java.io.IOException import java.util.concurrent.ConcurrentHashMap @@ -34,19 +33,18 @@ class ConfigurationManager( optional: Boolean ): T { try { - return configurationProvider.load(configClass, configAlias) { - if (optional) { - configClass.getDeclaredConstructor().newInstance() + return configurationProvider.load(configAlias, configClass) { + if (!optional) { + throw IllegalStateException("The '$configAlias' is required") } - throw IllegalStateException("The '$configAlias' is required") + configClass.getDeclaredConstructor().newInstance() } } catch (e: IOException) { throw IllegalStateException("Cannot read ${configClass.name} configuration for config alias '$configAlias'", e) } } - @Suppress("UNCHECKED_CAST") - fun StringSubstitutor.getConfigurationOrLoad( + fun getConfigurationOrLoad( configClass: Class, optional: Boolean ): T { @@ -56,7 +54,7 @@ class ConfigurationManager( }.let { 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 index 0978c9d2..c6da5c20 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IConfigurationProvider.kt @@ -15,6 +15,41 @@ package com.exactpro.th2.common.schema.configuration +import java.io.InputStream +import java.util.function.Function +import java.util.function.Supplier + interface IConfigurationProvider { - fun load(configClass: Class, alias: String, default: () -> T): 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 + * @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/impl/JsonConfigurationProvider.kt b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/JsonConfigurationProvider.kt index 6621db35..62b16d60 100644 --- 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 @@ -23,39 +23,30 @@ 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 -import kotlin.io.path.isDirectory -import kotlin.io.path.isRegularFile class JsonConfigurationProvider( - private val baseDir: Path, - private val customPaths: Map = emptyMap(), + internal val baseDir: Path, + internal val customPaths: Map = emptyMap(), ) : IConfigurationProvider { - init { - require(baseDir.isDirectory()) { - "The '$baseDir' base directory doesn't exist or isn't directory" - } - customPaths.forEach { (alias, path) -> - require(path.isRegularFile()) { - "The '$path' path to the '$alias' alias doesn't exist or isn't regular file" - } - } - } - private val cache = ConcurrentHashMap() operator fun get(alias: String): Path = customPaths[alias] ?: baseDir.resolve("${alias}.${EXTENSION}") - @Suppress("UNCHECKED_CAST") - override fun load(configClass: Class, alias: String, default: () -> T): T = + override fun load(alias: String, configClass: Class, default: Supplier?): T = requireNotNull(cache.compute(alias) { _, value -> when { - value == null -> loadFromFile(configClass, alias, default) - !configClass.isInstance(value) -> loadFromFile(configClass, alias, default).also { + 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 @@ -65,24 +56,33 @@ class JsonConfigurationProvider( "Stored configuration instance of $alias config alias mismatches, " + "expected: ${configClass.canonicalName}, actual: ${value::class.java.canonicalName}" } - } as T + }.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(configClass: Class, alias: String, default: () -> T): T { + private fun loadFromFile(alias: String, default: Supplier?, parser: Function): T { val configPath = this[alias] if (!configPath.exists()) { - K_LOGGER.debug { "'$configPath' file related to the '$alias' config alias doesn't exist" } - return default() + 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() + 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(MAPPER.readValue(content, configClass)) { - "Parsed format of the '$alias' config alias content can't be null" + return requireNotNull(parser.apply(content.byteInputStream())) { + "Parsed format of config content can't be null, alias: '$alias'" } } @@ -102,7 +102,7 @@ class JsonConfigurationProvider( .configure(KotlinFeature.NullToEmptyCollection, false) .configure(KotlinFeature.NullToEmptyMap, false) .configure(KotlinFeature.NullIsSameAsDefault, false) - .configure(KotlinFeature.SingletonSupport, false) + .configure(KotlinFeature.SingletonSupport, true) .configure(KotlinFeature.StrictNullChecks, false) .build(), RoutingStrategyModule(this), 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 47c0c656..d1eeee35 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/factory/CommonFactoryTest.kt b/src/test/kotlin/com/exactpro/th2/common/schema/factory/CommonFactoryTest.kt index bc4c1d7b..96b788c2 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 @@ -14,17 +14,13 @@ */ 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.cradle.CradleConfidentialConfiguration -import com.exactpro.th2.common.schema.cradle.CradleNonConfidentialConfiguration +import com.exactpro.th2.common.schema.configuration.impl.JsonConfigurationProvider +import com.exactpro.th2.common.schema.factory.AbstractCommonFactory.CUSTOM_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.BOX_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.CONFIG_DEFAULT_PATH import com.exactpro.th2.common.schema.factory.CommonFactory.CONNECTION_MANAGER_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.CRADLE_CONFIDENTIAL_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.CRADLE_NON_CONFIDENTIAL_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.CUSTOM_CFG_ALIAS 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.GRPC_CFG_ALIAS @@ -33,100 +29,398 @@ import com.exactpro.th2.common.schema.factory.CommonFactory.RABBIT_MQ_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.ROUTER_GRPC_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.ROUTER_MQ_CFG_ALIAS import com.exactpro.th2.common.schema.factory.CommonFactory.TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY -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 org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.junitpioneer.jupiter.ClearSystemProperty import org.junitpioneer.jupiter.SetSystemProperty import java.nio.file.Path +import kotlin.reflect.cast import kotlin.test.assertEquals import kotlin.test.assertNotNull class CommonFactoryTest { - @Test - fun `test load config by default path (default constructor)`() { - CommonFactory().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 -> + assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) + assertConfigs(commonFactory) + } } - } - @Test - fun `test load config by default path (createFromArguments(empty))`() { - CommonFactory.createFromArguments().use { commonFactory -> - assertConfigs(commonFactory, CONFIG_DEFAULT_PATH) + @Test + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + fun `test with system property`() { + CommonFactory().use { commonFactory -> + assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + } } } - @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)) + @Nested + @ClearSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY) + inner class CreateBySettingsConstructor { + @Test + fun `test default`() { + CommonFactory(FactorySettings()).use { commonFactory -> + assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) + assertConfigs(commonFactory) + } } - } - @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)) + @ParameterizedTest + @ValueSource(strings = [ + ALIASES_DICTIONARY_NAME, + TYPES_DICTIONARY_NAME, + OLD_DICTIONARY_NAME + ]) + fun `test custom dictionary path`(name: String) { + val factorySettings = FactorySettings().apply { + setCustomDictionaryPath(name) + } + CommonFactory(factorySettings).use { commonFactory -> + assertCustomDictionaryPath(name, CONFIG_DEFAULT_PATH, commonFactory) + assertConfigs(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 + @ValueSource(strings = [ + RABBIT_MQ_CFG_ALIAS, + ROUTER_MQ_CFG_ALIAS, + CONNECTION_MANAGER_CFG_ALIAS, + GRPC_CFG_ALIAS, + ROUTER_GRPC_CFG_ALIAS, + CRADLE_CONFIDENTIAL_CFG_ALIAS, + CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, + PROMETHEUS_CFG_ALIAS, + BOX_CFG_ALIAS, + CUSTOM_CFG_ALIAS, + ]) + fun `test custom path for config`(alias: String) { + val factorySettings = FactorySettings().apply { + setCustomPathForConfig(alias) + } + CommonFactory(factorySettings).use { commonFactory -> + assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) + assertCustomPathForConfig(commonFactory, CONFIG_DEFAULT_PATH, alias) + } } - } - @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)) + @Test + fun `test with custom config path`() { + CommonFactory(FactorySettings().apply { + baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() + }).use { commonFactory -> + assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + } + } + + @ParameterizedTest + @ValueSource(strings = [ + ALIASES_DICTIONARY_NAME, + TYPES_DICTIONARY_NAME, + OLD_DICTIONARY_NAME + ]) + fun `test with custom config path and custom dictionary path`(name: String) { + CommonFactory(FactorySettings().apply { + baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() + setCustomDictionaryPath(name) + }).use { commonFactory -> + assertCustomDictionaryPath(name, CMD_ARG_CONFIG_DIR.toPath(), commonFactory) + assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + } + } + + @ParameterizedTest + @ValueSource(strings = [ + RABBIT_MQ_CFG_ALIAS, + ROUTER_MQ_CFG_ALIAS, + CONNECTION_MANAGER_CFG_ALIAS, + GRPC_CFG_ALIAS, + ROUTER_GRPC_CFG_ALIAS, + CRADLE_CONFIDENTIAL_CFG_ALIAS, + CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, + PROMETHEUS_CFG_ALIAS, + BOX_CFG_ALIAS, + CUSTOM_CFG_ALIAS, + ]) + fun `test with custom config path and custom path for config`(alias: String) { + CommonFactory(FactorySettings().apply { + baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() + setCustomPathForConfig(alias) + }).use { commonFactory -> + assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) + assertCustomPathForConfig(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), alias) + } + } + + @Test + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + fun `test with system property`() { + CommonFactory(FactorySettings()).use { commonFactory -> + assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + } + } + + @ParameterizedTest + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + @ValueSource(strings = [ + ALIASES_DICTIONARY_NAME, + TYPES_DICTIONARY_NAME, + OLD_DICTIONARY_NAME + ]) + fun `test with system property and custom dictionary path`(name: String) { + val factorySettings = FactorySettings().apply { + setCustomDictionaryPath(name) + } + CommonFactory(factorySettings).use { commonFactory -> + assertCustomDictionaryPath(name, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), commonFactory) + assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + } + } + + @ParameterizedTest + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + @ValueSource(strings = [ + RABBIT_MQ_CFG_ALIAS, + ROUTER_MQ_CFG_ALIAS, + CONNECTION_MANAGER_CFG_ALIAS, + GRPC_CFG_ALIAS, + ROUTER_GRPC_CFG_ALIAS, + CRADLE_CONFIDENTIAL_CFG_ALIAS, + CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, + PROMETHEUS_CFG_ALIAS, + BOX_CFG_ALIAS, + CUSTOM_CFG_ALIAS, + ]) + fun `test with system property and custom path for config`(alias: String) { + val factorySettings = FactorySettings().apply { + setCustomPathForConfig(alias) + } + CommonFactory(factorySettings).use { commonFactory -> + assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) + assertCustomPathForConfig(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), alias) + } } - } + @Test + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + fun `test with cmd config argument and system property`() { + CommonFactory(FactorySettings().apply { + baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() + }).use { commonFactory -> + assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + } + } + + @ParameterizedTest + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + @ValueSource(strings = [ + ALIASES_DICTIONARY_NAME, + TYPES_DICTIONARY_NAME, + OLD_DICTIONARY_NAME + ]) + fun `test with cmd config argument and system property and custom dictionary path`(name: String) { + CommonFactory(FactorySettings().apply { + baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() + setCustomDictionaryPath(name) + }).use { commonFactory -> + assertCustomDictionaryPath(name, CMD_ARG_CONFIG_DIR.toPath(), commonFactory) + assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + } + } - 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") + @ParameterizedTest + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + @ValueSource(strings = [ + RABBIT_MQ_CFG_ALIAS, + ROUTER_MQ_CFG_ALIAS, + CONNECTION_MANAGER_CFG_ALIAS, + GRPC_CFG_ALIAS, + ROUTER_GRPC_CFG_ALIAS, + CRADLE_CONFIDENTIAL_CFG_ALIAS, + CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, + PROMETHEUS_CFG_ALIAS, + BOX_CFG_ALIAS, + CUSTOM_CFG_ALIAS, + ]) + fun `test with cmd config argument and system property and custom path for config`(alias: String) { + CommonFactory(FactorySettings().apply { + baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() + setCustomPathForConfig(alias) + }).use { commonFactory -> + assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) + assertCustomPathForConfig(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), alias) + } } - assertConfigurationManager(commonFactory, configPath) } - private fun assertConfigurationManager(commonFactory: CommonFactory, configPath: Path) { - CONFIG_CLASSES.forEach { (clazz, alias) -> - 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 -> + assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) + assertConfigs(commonFactory) + } + } + + @Test + fun `test with cmd config argument`() { + CommonFactory.createFromArguments("-c", CMD_ARG_CONFIG_DIR).use { commonFactory -> + assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + } + } + + @Test + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + fun `test with system property`() { + CommonFactory.createFromArguments().use { commonFactory -> + assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + } + } + + @Test + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + fun `test with cmd config argument and system property`() { + CommonFactory.createFromArguments("-c", CMD_ARG_CONFIG_DIR).use { commonFactory -> + assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) + assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + } } } companion object { - private const val CONFIG_DIR_IN_RESOURCE = "src/test/resources/test_common_factory_load_configs" + private const val CMD_ARG_CONFIG_DIR = "src/test/resources/test_cmd_arg_config" + private const val SYSTEM_PROPERTY_CONFIG_DIR = "src/test/resources/test_system_property_config" + private val CUSTOM_DIR = Path.of("dictionary/custom/path") + + private const val ALIASES_DICTIONARY_NAME = "aliases" + private const val TYPES_DICTIONARY_NAME = "types" + private const val OLD_DICTIONARY_NAME = "old" + + private val CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER: Map = hashMapOf( + ALIASES_DICTIONARY_NAME to DictionaryDirMetadata(DICTIONARY_ALIAS_DIR_NAME, FactorySettings::dictionaryAliasesDir, CommonFactory::getPathToDictionaryAliasesDir), + TYPES_DICTIONARY_NAME to DictionaryDirMetadata(DICTIONARY_TYPE_DIR_NAME, FactorySettings::dictionaryTypesDir, CommonFactory::getPathToDictionaryTypesDir), + OLD_DICTIONARY_NAME to DictionaryDirMetadata("", FactorySettings::oldDictionariesDir, CommonFactory::getOldPathToDictionariesDir), + ) + + private val CONFIG_ALIASES: Map = hashMapOf( + RABBIT_MQ_CFG_ALIAS to ConfigMetadata(FactorySettings::rabbitMQ), + ROUTER_MQ_CFG_ALIAS to ConfigMetadata(FactorySettings::routerMQ), + CONNECTION_MANAGER_CFG_ALIAS to ConfigMetadata(FactorySettings::connectionManagerSettings), + GRPC_CFG_ALIAS to ConfigMetadata(FactorySettings::grpc), + ROUTER_GRPC_CFG_ALIAS to ConfigMetadata(FactorySettings::routerGRPC), + CRADLE_CONFIDENTIAL_CFG_ALIAS to ConfigMetadata(FactorySettings::cradleConfidential), + CRADLE_NON_CONFIDENTIAL_CFG_ALIAS to ConfigMetadata(FactorySettings::cradleNonConfidential), + PROMETHEUS_CFG_ALIAS to ConfigMetadata(FactorySettings::prometheus), + BOX_CFG_ALIAS to ConfigMetadata(FactorySettings::boxConfiguration), + CUSTOM_CFG_ALIAS to ConfigMetadata(FactorySettings::custom), + ) + + private fun String.toPath() = Path.of(this) + + private fun assertDictionaryDir(commonFactory: CommonFactory, configPath: Path) { + CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.forEach { (name, dictionaryDirMetadata) -> + assertEquals( + configPath.resolve(dictionaryDirMetadata.dirName), + dictionaryDirMetadata.getPath.invoke(commonFactory), + "Configured config path: $configPath, config name: $name" + ) + } + } + + private fun assertConfigs(commonFactory: CommonFactory) { + val provider = assertProvider(commonFactory) + assertEquals(CONFIG_DEFAULT_PATH, provider.baseDir) + assertEquals(0, provider.customPaths.size) + } + private fun assertConfigs(commonFactory: CommonFactory, configPath: Path, customPaths: Boolean) { + val provider = assertProvider(commonFactory) + assertEquals(configPath, provider.baseDir) + + if (customPaths) { + assertEquals(CONFIG_ALIASES.size, provider.customPaths.size) + + CONFIG_ALIASES.keys.forEach { alias -> + val path = assertNotNull(provider.customPaths[alias]) + assertEquals(configPath, path.parent, "Configured config path: $configPath") + } + } else { + assertEquals(0, provider.customPaths.size) + } + } + + private fun assertProvider(commonFactory: CommonFactory): JsonConfigurationProvider { + assertEquals(JsonConfigurationProvider::class, commonFactory.configurationProvider::class) + return JsonConfigurationProvider::class.cast(commonFactory.getConfigurationProvider()) + } + + private fun assertCustomPathForConfig(commonFactory: CommonFactory, basePath: Path, alias: String) { + val provider = assertProvider(commonFactory) + assertEquals(basePath, provider.baseDir) + assertEquals(1, provider.customPaths.size) + assertEquals(CUSTOM_DIR, provider.customPaths[alias]) + } + + private fun FactorySettings.setCustomPathForConfig(alias: String) { + CONFIG_ALIASES.asSequence() + .find { (optionName, _) -> optionName == alias } + ?.value?.setPath?.invoke(this, CUSTOM_DIR) + } + + private fun assertCustomDictionaryPath( + name: String, + basePath: Path, + commonFactory: CommonFactory + ) { + CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.forEach { (optionName, dictionaryDirMetadata) -> + if (optionName == name) { + assertEquals( + CUSTOM_DIR, + dictionaryDirMetadata.getPath.invoke(commonFactory), + "Configured config path: $CUSTOM_DIR, config optionName: $optionName" + ) + } else { + assertEquals( + basePath.resolve(dictionaryDirMetadata.dirName), + dictionaryDirMetadata.getPath.invoke(commonFactory), + "Configured config path: $basePath, config optionName: $optionName" + ) + } + } + } + + private fun FactorySettings.setCustomDictionaryPath( + name: String + ) { + CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.asSequence() + .find { (optionName, _) -> optionName == name } + ?.value?.setPath?.invoke(this, CUSTOM_DIR) + } - private val CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER: Map Path> = mapOf( - CUSTOM_CFG_ALIAS to { pathToCustomConfiguration }, - DICTIONARY_ALIAS_DIR_NAME to { pathToDictionaryAliasesDir }, - DICTIONARY_TYPE_DIR_NAME to { pathToDictionaryTypesDir }, + private data class DictionaryDirMetadata( + val dirName: String, + val setPath: FactorySettings.(Path) -> Unit, + val getPath: CommonFactory.() -> Path, ) - private val CONFIG_CLASSES: Map, String> = mapOf( - RabbitMQConfiguration::class.java to RABBIT_MQ_CFG_ALIAS, - MessageRouterConfiguration::class.java to ROUTER_MQ_CFG_ALIAS, - ConnectionManagerConfiguration::class.java to CONNECTION_MANAGER_CFG_ALIAS, - GrpcConfiguration::class.java to GRPC_CFG_ALIAS, - GrpcRouterConfiguration::class.java to ROUTER_GRPC_CFG_ALIAS, - CradleConfidentialConfiguration::class.java to CRADLE_CONFIDENTIAL_CFG_ALIAS, - CradleNonConfidentialConfiguration::class.java to CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, - CassandraStorageSettings::class.java to CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, - PrometheusConfiguration::class.java to PROMETHEUS_CFG_ALIAS, - BoxConfiguration::class.java to BOX_CFG_ALIAS, + private data class ConfigMetadata( + val setPath: FactorySettings.(Path) -> Unit, ) } } \ 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_system_property_config/custom.json b/src/test/resources/test_system_property_config/custom.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/src/test/resources/test_system_property_config/custom.json @@ -0,0 +1 @@ +{} \ No newline at end of file From d3cb3b57a386635518b30f4fce2a77430604e5a6 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 30 Oct 2023 11:01:42 +0400 Subject: [PATCH 05/10] Corrected after review --- .../common/schema/factory/CommonFactory.java | 32 +++++++++---------- .../microservice/IApplicationFactory.kt | 3 +- .../impl/JsonConfigurationProvider.kt | 7 ++-- 3 files changed, 22 insertions(+), 20 deletions(-) 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 e5849ab6..c2c44d48 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 @@ -237,7 +237,7 @@ public static CommonFactory createFromArguments(String... args) { try { CommandLine cmd = new DefaultParser().parse(options, args); - Path configs = calculateCmdPath(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()); @@ -275,19 +275,19 @@ public static CommonFactory createFromArguments(String... args) { configureLogger(configs); } } - settings.setRabbitMQ(calculateCmdPath(cmd.getOptionValue(rabbitConfigurationOption.getLongOpt()))); - settings.setRouterMQ(calculateCmdPath(cmd.getOptionValue(messageRouterConfigurationOption.getLongOpt()))); - settings.setConnectionManagerSettings(calculateCmdPath(cmd.getOptionValue(connectionManagerConfigurationOption.getLongOpt()))); - settings.setGrpc(calculateCmdPath(defaultIfNull(cmd.getOptionValue(grpcConfigurationOption.getLongOpt()), cmd.getOptionValue(grpcRouterConfigurationOption.getLongOpt())))); - settings.setRouterGRPC(calculateCmdPath(cmd.getOptionValue(grpcRouterConfigOption.getLongOpt()))); - settings.setCradleConfidential(calculateCmdPath(cmd.getOptionValue(cradleConfidentialConfigurationOption.getLongOpt()))); - settings.setCradleNonConfidential(calculateCmdPath(defaultIfNull(cmd.getOptionValue(cradleManagerConfigurationOption.getLongOpt()), cmd.getOptionValue(cradleConfigurationOption.getLongOpt())))); - settings.setPrometheus(calculateCmdPath(cmd.getOptionValue(prometheusConfigurationOption.getLongOpt()))); - settings.setBoxConfiguration(calculateCmdPath(cmd.getOptionValue(boxConfigurationOption.getLongOpt()))); - settings.setCustom(calculateCmdPath(cmd.getOptionValue(customConfigurationOption.getLongOpt()))); - settings.setDictionaryTypesDir(calculateCmdPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt()))); - settings.setDictionaryAliasesDir(calculateCmdPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt()))); - settings.setOldDictionariesDir(calculateCmdPath(cmd.getOptionValue(dictionariesDirOption.getLongOpt()))); + 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) { @@ -691,7 +691,7 @@ private static Path defaultPathIfNull(Path customPath, Path basePath, String nam private static void putIfNotNull(@NotNull Map paths, @NotNull String alias, @Nullable Path path) { requireNonNull(paths, "'Paths' can't be null"); - requireNonNull(paths, "'Alias' can't be null"); + requireNonNull(alias, "'Alias' can't be null"); if (path != null) { paths.put(alias, path); } @@ -716,7 +716,7 @@ private static Option createLongOption(Options options, String optionName) { return option; } - private static @Nullable Path calculateCmdPath(@Nullable String path) { + private static @Nullable Path toPath(@Nullable String path) { return path == null ? null : Path.of(path); } diff --git a/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt index 7ad0473c..de53c7ad 100644 --- a/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt +++ b/src/main/kotlin/com/exactpro/th2/common/microservice/IApplicationFactory.kt @@ -48,7 +48,8 @@ interface IApplicationFactory : AutoCloseable { private const val APPLICATION_FACTORY_SYSTEM_PROPERTY = "th2.microservice.application-factory" private const val MONITOR_NAME = "microservice_main" @JvmStatic - fun run(factory: IApplicationFactory?, vararg args: String) { + @JvmOverloads + fun run(factory: IApplicationFactory? = null, vararg args: String) { val liveness: MetricMonitor = registerLiveness(MONITOR_NAME) val readiness: MetricMonitor = registerReadiness(MONITOR_NAME) 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 index 62b16d60..110a4c7f 100644 --- 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 @@ -31,14 +31,15 @@ import java.util.function.Function import java.util.function.Supplier import kotlin.io.path.exists -class JsonConfigurationProvider( + +class JsonConfigurationProvider @JvmOverloads constructor( internal val baseDir: Path, internal val customPaths: Map = emptyMap(), ) : IConfigurationProvider { private val cache = ConcurrentHashMap() - operator fun get(alias: String): Path = customPaths[alias] ?: baseDir.resolve("${alias}.${EXTENSION}") + 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 -> @@ -66,7 +67,7 @@ class JsonConfigurationProvider( } private fun loadFromFile(alias: String, default: Supplier?, parser: Function): T { - val configPath = this[alias] + 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() From 4d83df0a43c4d8ed26f431dc18d8e4c909514754 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 30 Oct 2023 13:59:16 +0400 Subject: [PATCH 06/10] tmp --- .../schema/factory/AbstractCommonFactory.java | 15 ++++++++++++++ .../common/schema/factory/CommonFactory.java | 20 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) 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 f3ab2a7a..6ac541de 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 @@ -36,10 +36,12 @@ 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,6 +57,10 @@ 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.util.Log4jConfigUtils; @@ -131,6 +137,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 * 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 c2c44d48..d7250ddc 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 @@ -121,6 +121,14 @@ public class CommonFactory extends AbstractCommonFactory { private static final Logger LOGGER = LoggerFactory.getLogger(CommonFactory.class.getName()); + private CommonFactory(IConfigurationProvider configurationProvider) { + super(); + dictionaryTypesDir = defaultPathIfNull(configurationProvider, DICTIONARY_TYPE_DIR_NAME); + dictionaryAliasesDir = defaultPathIfNull(settings.getDictionaryAliasesDir(), settings.getBaseConfigDir(), DICTIONARY_ALIAS_DIR_NAME); + oldDictionariesDir = defaultPathIfNull(settings.getOldDictionariesDir(), settings.getBaseConfigDir(), ""); + } + + @Deprecated(since = "6", forRemoval = true) public CommonFactory(FactorySettings settings) { super(settings); dictionaryTypesDir = defaultPathIfNull(settings.getDictionaryTypesDir(), settings.getBaseConfigDir(), DICTIONARY_TYPE_DIR_NAME); @@ -156,10 +164,18 @@ protected ConfigurationManager getConfigurationManager() { return configurationManager; } - IConfigurationProvider getConfigurationProvider() { + public IConfigurationProvider getConfigurationProvider() { return configurationProvider; } + public static CommonFactory createFromProvider(IConfigurationProvider provider) { + FactorySettings settings = new FactorySettings(); + + + + return new CommonFactory(settings); + } + /** * Create {@link CommonFactory} from command line arguments * @@ -302,6 +318,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); } @@ -314,6 +331,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()); } From 19cd19ced42fdc6d2d0290c005a58679aa0f2dd7 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 14 Jun 2024 12:28:55 +0400 Subject: [PATCH 07/10] Added IDictionaryProvider --- .../schema/factory/AbstractCommonFactory.java | 19 +- .../common/schema/factory/CommonFactory.java | 168 +++----------- .../configuration/IDictionaryProvider.kt | 28 +++ .../configuration/impl/DictionaryProvider.kt | 218 ++++++++++++++++++ 4 files changed, 278 insertions(+), 155 deletions(-) create mode 100644 src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt create mode 100644 src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/DictionaryProvider.kt 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 248abbab..d2063237 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 @@ -522,7 +522,9 @@ public T getCustomConfiguration(Class confClass) { * * @return Dictionary as {@link InputStream} * @throws IllegalStateException if can not read dictionary or found more than one target + * @deprecated please use {@link #loadDictionary(String)} */ + @Deprecated(since = "6", forRemoval = true) public abstract InputStream loadSingleDictionary(); /** @@ -551,7 +553,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); @@ -588,20 +591,6 @@ private EventID createRootEventID() throws IOException { protected abstract ConfigurationManager getConfigurationManager(); - /** - * @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 d7250ddc..e612cb4d 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 @@ -20,6 +20,9 @@ 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; @@ -44,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; @@ -52,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; @@ -62,15 +63,12 @@ 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 org.apache.commons.lang3.ObjectUtils.defaultIfNull; @@ -113,52 +111,29 @@ 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 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()); - private CommonFactory(IConfigurationProvider configurationProvider) { + private CommonFactory(@NotNull IConfigurationProvider configurationProvider, @NotNull IDictionaryProvider dictionaryProvider) { super(); - dictionaryTypesDir = defaultPathIfNull(configurationProvider, DICTIONARY_TYPE_DIR_NAME); - dictionaryAliasesDir = defaultPathIfNull(settings.getDictionaryAliasesDir(), settings.getBaseConfigDir(), DICTIONARY_ALIAS_DIR_NAME); - oldDictionariesDir = defaultPathIfNull(settings.getOldDictionariesDir(), settings.getBaseConfigDir(), ""); + this.dictionaryProvider = requireNonNull(dictionaryProvider, "Dictionary provider can't be null"); + this.configurationProvider = requireNonNull(configurationProvider, "Configuration provider can't be null"); + configurationManager = createConfigurationManager(configurationProvider); + start(); } @Deprecated(since = "6", forRemoval = true) public CommonFactory(FactorySettings settings) { - super(settings); - dictionaryTypesDir = defaultPathIfNull(settings.getDictionaryTypesDir(), settings.getBaseConfigDir(), DICTIONARY_TYPE_DIR_NAME); - dictionaryAliasesDir = defaultPathIfNull(settings.getDictionaryAliasesDir(), settings.getBaseConfigDir(), DICTIONARY_ALIAS_DIR_NAME); - oldDictionariesDir = defaultPathIfNull(settings.getOldDictionariesDir(), settings.getBaseConfigDir(), ""); - - configurationProvider = createConfigurationProvider(settings); - configurationManager = createConfigurationManager(configurationProvider); - start(); + this(createConfigurationProvider(settings), createDictionaryProvider(settings)); } public CommonFactory() { this(new FactorySettings()); } - @Override - protected Path getPathToDictionaryTypesDir() { - return dictionaryTypesDir; - } - - @Override - protected Path getPathToDictionaryAliasesDir() { - return dictionaryAliasesDir; - } - - @Override - protected Path getOldPathToDictionariesDir() { - return oldDictionariesDir; - } - @Override protected ConfigurationManager getConfigurationManager() { return configurationManager; @@ -453,122 +428,27 @@ 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()); - } - } - - 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()); - } - } - - 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"); - } - - var targetDictionary = dictionaries.get(0); - - return new ByteArrayInputStream(getGzipBase64StringDecoder().decode(Files.readString(targetDictionary))); - } catch (IOException e) { - throw new IllegalStateException("Can not read dictionary", e); - } + return dictionaryProvider.load(dictionaryType); } static @NotNull Path getConfigPath() { @@ -674,6 +554,14 @@ private static void createDirectory(Path dir) throws IOException { } } + 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 IConfigurationProvider createConfigurationProvider(FactorySettings settings) { Map paths = new HashMap<>(); putIfNotNull(paths, CUSTOM_CFG_ALIAS, settings.getCustom()); @@ -707,11 +595,11 @@ private static Path defaultPathIfNull(Path customPath, Path basePath, String nam return customPath == null ? getConfigPath(basePath).resolve(name) : customPath; } - private static void putIfNotNull(@NotNull Map paths, @NotNull String alias, @Nullable Path path) { + private static void putIfNotNull(@NotNull Map paths, @NotNull T key, @Nullable Path path) { requireNonNull(paths, "'Paths' can't be null"); - requireNonNull(alias, "'Alias' can't be null"); + requireNonNull(key, "'Key' can't be null"); if (path != null) { - paths.put(alias, path); + paths.put(key, path); } } 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 00000000..7ee0c848 --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt @@ -0,0 +1,28 @@ +/* + * 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 + @Deprecated("Load single dictionary is deprecated, please use load by alias") + 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 00000000..3d4ad2ed --- /dev/null +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/impl/DictionaryProvider.kt @@ -0,0 +1,218 @@ +/* + * 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 kotlin.io.path.exists +import kotlin.io.path.isDirectory +import kotlin.io.path.isRegularFile +import kotlin.streams.asSequence + +class DictionaryProvider @JvmOverloads constructor( + private val baseDir: Path, + paths: Map = emptyMap() +): IDictionaryProvider { + init { + require(baseDir.exists()) { "Base dir '$baseDir' doesn't exist" } + require(baseDir.isDirectory()) { "Base dir '$baseDir' isn't a dictionary" } + } + + 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() + } + return Files.walk(dictionaryAliasPath).asSequence() + .filter(Path::isRegularFile) + .map(::toAlias) + .toSet() + } 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()) { + emptyList() + } else { + Files.walk(dictionaryAliasPath).asSequence() + .filter(Path::isRegularFile) + .toList() + } + + if (files.isEmpty()) { + if (dictionaryTypePath.isDirectory()) { + files = Files.walk(dictionaryTypePath).asSequence() + .filter(Path::isDirectory) + .flatMap { dir -> + Files.walk(dir).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 emptyList() + } + return Files.walk(dictionaryOldPath).asSequence() + .filter(Path::isRegularFile) + .filter { file -> file.fileName.toString().contains(name) } + .toList() + } + + private fun searchInTypeDir(type: DictionaryType): List { + val path = type.getDictionary(dictionaryTypePath) + if (!path.isDirectory()) { + return emptyList() + } + return Files.walk(path).asSequence() + .filter(Path::isRegularFile) + .toList() + } + + private fun searchInAliasDir(alias: String): List { + if (!dictionaryAliasPath.isDirectory()) { + return emptyList() + } + return Files.walk(dictionaryAliasPath).asSequence() + .filter(Path::isRegularFile) + .filter { file -> alias.equals(toAlias(file), true) } + .toList() + } + + 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 { + require(baseDir.exists()) { "Base dir '$baseDir' doesn't exist" } + require(baseDir.isDirectory()) { "Base dir '$baseDir' isn't a dictionary" } + return buildMap { + DictionaryKind.values().forEach { + put(it, baseDir.resolve(it.directoryName).toAbsolutePath()) + } + } + } + } +} \ No newline at end of file From 9078577bf66750b56ed61a0ce42a56c8ad8975d8 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Fri, 14 Jun 2024 17:51:02 +0400 Subject: [PATCH 08/10] Refactored load dictionary tests --- .../common/schema/factory/CommonFactory.java | 22 +- .../th2/common/schema/TestDictionaryLoad.kt | 246 +++++++++++++++--- .../test_load_dictionaries/dictionaries/main | 1 - .../dictionaries/test_alias_1.encoded | 1 - .../dictionaries/test_alias_2 | 1 - .../dictionaries/test_alias_3.encoded | 1 - .../dictionaries/test_alias_4 | 1 - .../level1/test_dictionary.encoded | 1 - .../test_load_dictionaries/prometheus.json | 3 - .../single_dictionary/test_alias_1.encoded | 1 - .../test_dictionary_LEVEL1.encoded | 1 - .../test_dictionary_MAIN.encoded | 1 - 12 files changed, 223 insertions(+), 57 deletions(-) delete mode 100644 src/test/resources/test_load_dictionaries/dictionaries/main delete mode 100644 src/test/resources/test_load_dictionaries/dictionaries/test_alias_1.encoded delete mode 100644 src/test/resources/test_load_dictionaries/dictionaries/test_alias_2 delete mode 100644 src/test/resources/test_load_dictionaries/dictionaries/test_alias_3.encoded delete mode 100644 src/test/resources/test_load_dictionaries/dictionaries/test_alias_4 delete mode 100644 src/test/resources/test_load_dictionaries/level1/test_dictionary.encoded delete mode 100644 src/test/resources/test_load_dictionaries/prometheus.json delete mode 100644 src/test/resources/test_load_dictionaries/single_dictionary/test_alias_1.encoded delete mode 100644 src/test/resources/test_load_dictionaries/test_dictionary_LEVEL1.encoded delete mode 100644 src/test/resources/test_load_dictionaries/test_dictionary_MAIN.encoded 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 8fe4353c..a49f1362 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 @@ -61,6 +61,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -465,10 +466,23 @@ public Set getDictionaryAliases() { } try (Stream files = Files.list(dictionaryFolder)) { - return files + List fileList = files .filter(Files::isRegularFile) - .map(dictionary -> FilenameUtils.removeExtension(dictionary.getFileName().toString())) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); + + Set aliasSet = new HashSet<>(); + Map> duplicates = new HashMap<>(); + for (Path path : fileList) { + String alias = FilenameUtils.removeExtension(path.getFileName().toString()).toLowerCase(); + if (!aliasSet.add(alias)) { + duplicates.computeIfAbsent(alias, (ignore) -> new HashSet<>()) + .add(path.getFileName().toString()); + } + } + if (!duplicates.isEmpty()) { + throw new IllegalStateException("Dictionary directory contains files with the same name in different cases, files: " + duplicates + ", path: " + dictionaryFolder.toAbsolutePath()); + } + return aliasSet; } } catch (IOException e) { throw new IllegalStateException("Can not get dictionaries aliases from path: " + dictionaryFolder.toAbsolutePath(), e); diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt b/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt index 76fae29f..172299b9 100644 --- a/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt +++ b/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt @@ -17,85 +17,249 @@ 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 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 class TestDictionaryLoad { - @Test - fun `test file load dictionary`() { - val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries") + @TempDir + lateinit var tempDir: Path + + @BeforeEach + fun beforeEach() { + writePrometheus(tempDir) + } - factory.readDictionary().use { - assert(String(it.readAllBytes()) == "test file") + //--//--//--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 folder load dictionary`() { - val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries") + 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() + } + } + } - factory.readDictionary(DictionaryType.LEVEL1).use { - assert(String(it.readAllBytes()) == "test file") + @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 folder load dictionaries by alias`() { - val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries") + 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()) }) + } + } - Assertions.assertDoesNotThrow { - factory.loadDictionary("test_alias_2").use { - assert(String(it.readAllBytes()) == "test file") + @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) } } } - @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)) + @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 folder load single dictionary from folder with several`() { - val factory = CommonFactory.createFromArguments("-c", "src/test/resources/test_load_dictionaries") + 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()) }) + } + } - Assertions.assertThrows(IllegalStateException::class.java) { - factory.loadSingleDictionary() + @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 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") + 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) + } } - val customFactory = CommonFactory(customSettings) + } + + //--//--//--loadAliases--//--//--// - customFactory.loadSingleDictionary().use { - assert(String(it.readAllBytes()) == "test file") + @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 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") + fun `test dictionary aliases from alias dictionary dir - empty directory`() { + CommonFactory.createFromArguments("-c", tempDir.absolutePathString()).use { commonFactory -> + assertEquals(emptySet(), commonFactory.dictionaryAliases) } - val customFactory = CommonFactory(customSettings) + } - customFactory.readDictionary().use { - assert(String(it.readAllBytes()) == "test file") + @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/resources/test_load_dictionaries/dictionaries/main b/src/test/resources/test_load_dictionaries/dictionaries/main deleted file mode 100644 index 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- 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 c994fef1..00000000 --- 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 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- 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 0f3ff093..00000000 --- a/src/test/resources/test_load_dictionaries/test_dictionary_MAIN.encoded +++ /dev/null @@ -1 +0,0 @@ -H4sIAAAAAAAAACtJLS5RSMvMSQUAwWtk8gkAAAA= \ No newline at end of file From 0fde596bd7c9ed46146b085b1c988db928ac03d3 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Mon, 17 Jun 2024 18:52:18 +0400 Subject: [PATCH 09/10] Refactored CommonFactoryTest --- build.gradle | 1 + .../schema/factory/AbstractCommonFactory.java | 2 - .../common/schema/factory/CommonFactory.java | 15 +- .../configuration/IDictionaryProvider.kt | 1 - .../configuration/impl/DictionaryProvider.kt | 89 ++- ...ictionaryLoad.kt => DictionaryLoadTest.kt} | 15 +- .../schema/factory/CommonFactoryTest.kt | 654 +++++++++++------- 7 files changed, 463 insertions(+), 314 deletions(-) rename src/test/kotlin/com/exactpro/th2/common/schema/{TestDictionaryLoad.kt => DictionaryLoadTest.kt} (98%) diff --git a/build.gradle b/build.gradle index 0a756250..97b1c3a5 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/src/main/java/com/exactpro/th2/common/schema/factory/AbstractCommonFactory.java b/src/main/java/com/exactpro/th2/common/schema/factory/AbstractCommonFactory.java index d2063237..8b794f4c 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 @@ -522,9 +522,7 @@ public T getCustomConfiguration(Class confClass) { * * @return Dictionary as {@link InputStream} * @throws IllegalStateException if can not read dictionary or found more than one target - * @deprecated please use {@link #loadDictionary(String)} */ - @Deprecated(since = "6", forRemoval = true) public abstract InputStream loadSingleDictionary(); /** 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 511e6080..e0b025bf 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 @@ -65,8 +65,6 @@ import java.util.Base64; import java.util.EnumMap; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -145,12 +143,9 @@ public IConfigurationProvider getConfigurationProvider() { return configurationProvider; } - public static CommonFactory createFromProvider(IConfigurationProvider provider) { - FactorySettings settings = new FactorySettings(); - - - - return new CommonFactory(settings); + public static CommonFactory createFromProvider(@NotNull IConfigurationProvider configurationProvider, + @NotNull IDictionaryProvider dictionaryProvider) { + return new CommonFactory(configurationProvider, dictionaryProvider); } /** @@ -593,10 +588,6 @@ private static ConfigurationManager createConfigurationManager(IConfigurationPro return new ConfigurationManager(configurationProvider, paths); } - private static Path defaultPathIfNull(Path customPath, Path basePath, String name) { - return customPath == null ? getConfigPath(basePath).resolve(name) : customPath; - } - 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"); 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 index 7ee0c848..3121b9c2 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt @@ -23,6 +23,5 @@ interface IDictionaryProvider { fun load(alias: String): InputStream @Deprecated("Load dictionary by type is deprecated, please use load by alias") fun load(type: DictionaryType): InputStream - @Deprecated("Load single dictionary is deprecated, please use load by alias") 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 index 3d4ad2ed..3ccafdfe 100644 --- 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 @@ -27,19 +27,15 @@ import java.io.IOException import java.io.InputStream import java.nio.file.Files import java.nio.file.Path -import kotlin.io.path.exists +import java.util.Locale import kotlin.io.path.isDirectory import kotlin.io.path.isRegularFile import kotlin.streams.asSequence class DictionaryProvider @JvmOverloads constructor( - private val baseDir: Path, + baseDir: Path, paths: Map = emptyMap() -): IDictionaryProvider { - init { - require(baseDir.exists()) { "Base dir '$baseDir' doesn't exist" } - require(baseDir.isDirectory()) { "Base dir '$baseDir' isn't a dictionary" } - } +) : IDictionaryProvider { private val directoryPaths = DictionaryKind.createMapping(baseDir) .plus(paths.mapValues { (_, value) -> value.toAbsolutePath() }) @@ -61,15 +57,31 @@ class DictionaryProvider @JvmOverloads constructor( override fun aliases(): Set { try { - if(!dictionaryAliasPath.isDirectory()) { + if (!dictionaryAliasPath.isDirectory()) { return emptySet() } - return Files.walk(dictionaryAliasPath).asSequence() + + val fileList = Files.walk(dictionaryAliasPath, 1).asSequence() .filter(Path::isRegularFile) - .map(::toAlias) - .toSet() + .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) + throw IllegalStateException( + "Can not get dictionaries aliases from path: ${dictionaryAliasPath.toAbsolutePath()}", + e + ) } } @@ -88,7 +100,10 @@ class DictionaryProvider @JvmOverloads constructor( return open(file) } catch (e: IOException) { - throw IllegalStateException("Can not load dictionary by '$alias' alias from path: ${dictionaryAliasPath.toAbsolutePath()}", e) + throw IllegalStateException( + "Can not load dictionary by '$alias' alias from path: ${dictionaryAliasPath.toAbsolutePath()}", + e + ) } } @@ -117,19 +132,19 @@ class DictionaryProvider @JvmOverloads constructor( val dirs = listOf(dictionaryAliasPath, dictionaryTypePath) try { var files: List = if (dictionaryAliasPath.isDirectory()) { - emptyList() - } else { - Files.walk(dictionaryAliasPath).asSequence() + Files.walk(dictionaryAliasPath, 1).asSequence() .filter(Path::isRegularFile) .toList() + } else { + emptyList() } - + if (files.isEmpty()) { if (dictionaryTypePath.isDirectory()) { - files = Files.walk(dictionaryTypePath).asSequence() + files = Files.walk(dictionaryTypePath, 1).asSequence() .filter(Path::isDirectory) .flatMap { dir -> - Files.walk(dir).asSequence() + Files.walk(dir, 1).asSequence() .filter(Path::isRegularFile) }.toList() } @@ -163,33 +178,33 @@ class DictionaryProvider @JvmOverloads constructor( } private fun searchInOldDir(name: String): List { - if (!dictionaryOldPath.isDirectory()) { - return emptyList() + if (dictionaryOldPath.isDirectory()) { + return Files.walk(dictionaryOldPath, 1).asSequence() + .filter(Path::isRegularFile) + .filter { file -> file.fileName.toString().contains(name) } + .toList() } - return Files.walk(dictionaryOldPath).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 emptyList() + if (path.isDirectory()) { + return Files.walk(path, 1).asSequence() + .filter(Path::isRegularFile) + .toList() } - return Files.walk(path).asSequence() - .filter(Path::isRegularFile) - .toList() + return emptyList() } private fun searchInAliasDir(alias: String): List { - if (!dictionaryAliasPath.isDirectory()) { - return emptyList() + if (dictionaryAliasPath.isDirectory()) { + return Files.walk(dictionaryAliasPath, 1).asSequence() + .filter(Path::isRegularFile) + .filter { file -> alias.equals(toAlias(file), true) } + .toList() } - return Files.walk(dictionaryAliasPath).asSequence() - .filter(Path::isRegularFile) - .filter { file -> alias.equals(toAlias(file), true) } - .toList() + return emptyList() } companion object { @@ -206,8 +221,6 @@ enum class DictionaryKind( companion object { fun createMapping(baseDir: Path): Map { - require(baseDir.exists()) { "Base dir '$baseDir' doesn't exist" } - require(baseDir.isDirectory()) { "Base dir '$baseDir' isn't a dictionary" } return buildMap { DictionaryKind.values().forEach { put(it, baseDir.resolve(it.directoryName).toAbsolutePath()) diff --git a/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt b/src/test/kotlin/com/exactpro/th2/common/schema/DictionaryLoadTest.kt similarity index 98% rename from src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt rename to src/test/kotlin/com/exactpro/th2/common/schema/DictionaryLoadTest.kt index 172299b9..d8c0b9bc 100644 --- a/src/test/kotlin/com/exactpro/th2/common/schema/TestDictionaryLoad.kt +++ b/src/test/kotlin/com/exactpro/th2/common/schema/DictionaryLoadTest.kt @@ -32,7 +32,8 @@ import kotlin.io.path.writeBytes import kotlin.io.path.writeText import kotlin.test.assertEquals -class TestDictionaryLoad { +@Suppress("DEPRECATION") +class DictionaryLoadTest { @TempDir lateinit var tempDir: Path @@ -64,7 +65,7 @@ class TestDictionaryLoad { @ParameterizedTest @ValueSource(strings = ["MAIN", "main", "test-dictionary"]) - fun `test read dictionary from type dictionary dir`(fileName: String, ) { + 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()) }) @@ -87,7 +88,7 @@ class TestDictionaryLoad { @ParameterizedTest @ValueSource(strings = ["MAIN", "main", "MAIN.xml", "main.json"]) - fun `test read dictionary from alias dictionary dir`(fileName: String, ) { + 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()) }) @@ -120,7 +121,7 @@ class TestDictionaryLoad { @ParameterizedTest @ValueSource(strings = ["INCOMING", "incoming", "test-dictionary"]) - fun `test read dictionary by type from type dictionary dir`(fileName: String, ) { + 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 -> @@ -147,7 +148,7 @@ class TestDictionaryLoad { @ParameterizedTest @ValueSource(strings = ["INCOMING", "incoming", "INCOMING.xml", "incoming.json"]) - fun `test read dictionary by type from alias dictionary dir`(fileName: String, ) { + 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 -> @@ -193,7 +194,7 @@ class TestDictionaryLoad { @ParameterizedTest @ValueSource(strings = ["TEST-ALIAS", "test-alias"]) - fun `test load dictionary by alias from alias dictionary dir`(fileName: String, ) { + 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 -> @@ -255,7 +256,7 @@ class TestDictionaryLoad { cfgPath.resolve("prometheus.json").writeText("{\"enabled\":false}") } - private fun writeDictionary(path: Path): String? { + private fun writeDictionary(path: Path): String { val content = RandomStringUtils.randomAlphanumeric(10) path.parent.createDirectories() path.writeBytes(ArchiveUtils.getGzipBase64StringEncoder().encode(content)) 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 96b788c2..9aecfd34 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,52 +14,82 @@ */ package com.exactpro.th2.common.schema.factory +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.factory.AbstractCommonFactory.CUSTOM_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.BOX_CFG_ALIAS +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.CONNECTION_MANAGER_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.CRADLE_CONFIDENTIAL_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.CRADLE_NON_CONFIDENTIAL_CFG_ALIAS -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.GRPC_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.PROMETHEUS_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.RABBIT_MQ_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.ROUTER_GRPC_CFG_ALIAS -import com.exactpro.th2.common.schema.factory.CommonFactory.ROUTER_MQ_CFG_ALIAS 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.ValueSource +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 { + @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() + } + @Nested @ClearSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY) inner class CreateByDefaultConstructor { @Test fun `test default`() { CommonFactory().use { commonFactory -> - assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) - assertConfigs(commonFactory) + assertThrowsDictionaryDir(commonFactory) + assertThrowsConfigs(commonFactory) } } @Test - @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR) fun `test with system property`() { CommonFactory().use { commonFactory -> - assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + assertDictionaryDirs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH) + assertConfigs(commonFactory, SYSTEM_PROPERTY_CFG_DIR_PATH) } } } @@ -70,198 +100,161 @@ class CommonFactoryTest { @Test fun `test default`() { CommonFactory(FactorySettings()).use { commonFactory -> - assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) - assertConfigs(commonFactory) + assertThrowsDictionaryDir(commonFactory) + assertThrowsConfigs(commonFactory) } } @ParameterizedTest - @ValueSource(strings = [ - ALIASES_DICTIONARY_NAME, - TYPES_DICTIONARY_NAME, - OLD_DICTIONARY_NAME - ]) - fun `test custom dictionary path`(name: String) { - val factorySettings = FactorySettings().apply { - setCustomDictionaryPath(name) - } - CommonFactory(factorySettings).use { commonFactory -> - assertCustomDictionaryPath(name, CONFIG_DEFAULT_PATH, commonFactory) - assertConfigs(commonFactory) + @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) } } @ParameterizedTest - @ValueSource(strings = [ - RABBIT_MQ_CFG_ALIAS, - ROUTER_MQ_CFG_ALIAS, - CONNECTION_MANAGER_CFG_ALIAS, - GRPC_CFG_ALIAS, - ROUTER_GRPC_CFG_ALIAS, - CRADLE_CONFIDENTIAL_CFG_ALIAS, - CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, - PROMETHEUS_CFG_ALIAS, - BOX_CFG_ALIAS, - CUSTOM_CFG_ALIAS, - ]) - fun `test custom path for config`(alias: String) { - val factorySettings = FactorySettings().apply { - setCustomPathForConfig(alias) - } - CommonFactory(factorySettings).use { commonFactory -> - assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) - assertCustomPathForConfig(commonFactory, CONFIG_DEFAULT_PATH, alias) + @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_CONFIG_DIR.toPath() + baseConfigDir = CMD_ARG_CFG_DIR_PATH }).use { commonFactory -> - assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH) + assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH) } } @ParameterizedTest - @ValueSource(strings = [ - ALIASES_DICTIONARY_NAME, - TYPES_DICTIONARY_NAME, - OLD_DICTIONARY_NAME - ]) - fun `test with custom config path and custom dictionary path`(name: String) { - CommonFactory(FactorySettings().apply { - baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() - setCustomDictionaryPath(name) - }).use { commonFactory -> - assertCustomDictionaryPath(name, CMD_ARG_CONFIG_DIR.toPath(), commonFactory) - assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + @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 - @ValueSource(strings = [ - RABBIT_MQ_CFG_ALIAS, - ROUTER_MQ_CFG_ALIAS, - CONNECTION_MANAGER_CFG_ALIAS, - GRPC_CFG_ALIAS, - ROUTER_GRPC_CFG_ALIAS, - CRADLE_CONFIDENTIAL_CFG_ALIAS, - CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, - PROMETHEUS_CFG_ALIAS, - BOX_CFG_ALIAS, - CUSTOM_CFG_ALIAS, - ]) - fun `test with custom config path and custom path for config`(alias: String) { + @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_CONFIG_DIR.toPath() - setCustomPathForConfig(alias) + baseConfigDir = CMD_ARG_CFG_DIR_PATH + helper.setOption(this, configPath) }).use { commonFactory -> - assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) - assertCustomPathForConfig(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), alias) + assertDictionaryDirs(commonFactory, CMD_ARG_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_CONFIG_DIR) - fun `test with system property`() { + @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 -> - assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + 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_CONFIG_DIR) - @ValueSource(strings = [ - ALIASES_DICTIONARY_NAME, - TYPES_DICTIONARY_NAME, - OLD_DICTIONARY_NAME - ]) - fun `test with system property and custom dictionary path`(name: String) { + @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 { - setCustomDictionaryPath(name) + helper.setOption(this, CUSTOM_DIR_PATH) } CommonFactory(factorySettings).use { commonFactory -> - assertCustomDictionaryPath(name, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), commonFactory) - assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + 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_CONFIG_DIR) - @ValueSource(strings = [ - RABBIT_MQ_CFG_ALIAS, - ROUTER_MQ_CFG_ALIAS, - CONNECTION_MANAGER_CFG_ALIAS, - GRPC_CFG_ALIAS, - ROUTER_GRPC_CFG_ALIAS, - CRADLE_CONFIDENTIAL_CFG_ALIAS, - CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, - PROMETHEUS_CFG_ALIAS, - BOX_CFG_ALIAS, - CUSTOM_CFG_ALIAS, - ]) - fun `test with system property and custom path for config`(alias: String) { + @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 { - setCustomPathForConfig(alias) + helper.setOption(this, configPath) } CommonFactory(factorySettings).use { commonFactory -> - assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) - assertCustomPathForConfig(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), alias) + 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_CONFIG_DIR) + @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_CONFIG_DIR.toPath() + baseConfigDir = CMD_ARG_CFG_DIR_PATH }).use { commonFactory -> - assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + 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_CONFIG_DIR) - @ValueSource(strings = [ - ALIASES_DICTIONARY_NAME, - TYPES_DICTIONARY_NAME, - OLD_DICTIONARY_NAME - ]) - fun `test with cmd config argument and system property and custom dictionary path`(name: String) { - CommonFactory(FactorySettings().apply { - baseConfigDir = CMD_ARG_CONFIG_DIR.toPath() - setCustomDictionaryPath(name) - }).use { commonFactory -> - assertCustomDictionaryPath(name, CMD_ARG_CONFIG_DIR.toPath(), commonFactory) - assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + @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) + + assertConfigs(commonFactory, CMD_ARG_CFG_DIR_PATH) } } @ParameterizedTest - @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CONFIG_DIR) - @ValueSource(strings = [ - RABBIT_MQ_CFG_ALIAS, - ROUTER_MQ_CFG_ALIAS, - CONNECTION_MANAGER_CFG_ALIAS, - GRPC_CFG_ALIAS, - ROUTER_GRPC_CFG_ALIAS, - CRADLE_CONFIDENTIAL_CFG_ALIAS, - CRADLE_NON_CONFIDENTIAL_CFG_ALIAS, - PROMETHEUS_CFG_ALIAS, - BOX_CFG_ALIAS, - CUSTOM_CFG_ALIAS, - ]) - fun `test with cmd config argument and system property and custom path for config`(alias: String) { + @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_CONFIG_DIR.toPath() - setCustomPathForConfig(alias) + baseConfigDir = CMD_ARG_CFG_DIR_PATH + helper.setOption(this, configPath) }).use { commonFactory -> - assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) - assertCustomPathForConfig(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), alias) + assertDictionaryDirs(commonFactory, CMD_ARG_CFG_DIR_PATH) + + val cfgBean = helper.writeConfigByCustomPath(configPath) + assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean)) } } } @@ -272,97 +265,132 @@ class CommonFactoryTest { @Test fun `test without parameters`() { CommonFactory.createFromArguments().use { commonFactory -> - assertDictionaryDir(commonFactory, CONFIG_DEFAULT_PATH) - assertConfigs(commonFactory) + assertThrowsDictionaryDir(commonFactory) + assertThrowsConfigs(commonFactory) } } @Test fun `test with cmd config argument`() { - CommonFactory.createFromArguments("-c", CMD_ARG_CONFIG_DIR).use { commonFactory -> - assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + 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_CONFIG_DIR) + @SetSystemProperty(key = TH2_COMMON_CONFIGURATION_DIRECTORY_SYSTEM_PROPERTY, value = SYSTEM_PROPERTY_CFG_DIR) fun `test with system property`() { CommonFactory.createFromArguments().use { commonFactory -> - assertDictionaryDir(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, SYSTEM_PROPERTY_CONFIG_DIR.toPath(), false) + 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_CONFIG_DIR) + @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_CONFIG_DIR).use { commonFactory -> - assertDictionaryDir(commonFactory, CMD_ARG_CONFIG_DIR.toPath()) - assertConfigs(commonFactory, CMD_ARG_CONFIG_DIR.toPath(), false) + 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 CMD_ARG_CONFIG_DIR = "src/test/resources/test_cmd_arg_config" - private const val SYSTEM_PROPERTY_CONFIG_DIR = "src/test/resources/test_system_property_config" - private val CUSTOM_DIR = Path.of("dictionary/custom/path") - - private const val ALIASES_DICTIONARY_NAME = "aliases" - private const val TYPES_DICTIONARY_NAME = "types" - private const val OLD_DICTIONARY_NAME = "old" - - private val CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER: Map = hashMapOf( - ALIASES_DICTIONARY_NAME to DictionaryDirMetadata(DICTIONARY_ALIAS_DIR_NAME, FactorySettings::dictionaryAliasesDir, CommonFactory::getPathToDictionaryAliasesDir), - TYPES_DICTIONARY_NAME to DictionaryDirMetadata(DICTIONARY_TYPE_DIR_NAME, FactorySettings::dictionaryTypesDir, CommonFactory::getPathToDictionaryTypesDir), - OLD_DICTIONARY_NAME to DictionaryDirMetadata("", FactorySettings::oldDictionariesDir, CommonFactory::getOldPathToDictionariesDir), - ) - - private val CONFIG_ALIASES: Map = hashMapOf( - RABBIT_MQ_CFG_ALIAS to ConfigMetadata(FactorySettings::rabbitMQ), - ROUTER_MQ_CFG_ALIAS to ConfigMetadata(FactorySettings::routerMQ), - CONNECTION_MANAGER_CFG_ALIAS to ConfigMetadata(FactorySettings::connectionManagerSettings), - GRPC_CFG_ALIAS to ConfigMetadata(FactorySettings::grpc), - ROUTER_GRPC_CFG_ALIAS to ConfigMetadata(FactorySettings::routerGRPC), - CRADLE_CONFIDENTIAL_CFG_ALIAS to ConfigMetadata(FactorySettings::cradleConfidential), - CRADLE_NON_CONFIDENTIAL_CFG_ALIAS to ConfigMetadata(FactorySettings::cradleNonConfidential), - PROMETHEUS_CFG_ALIAS to ConfigMetadata(FactorySettings::prometheus), - BOX_CFG_ALIAS to ConfigMetadata(FactorySettings::boxConfiguration), - CUSTOM_CFG_ALIAS to ConfigMetadata(FactorySettings::custom), - ) + 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) - private fun assertDictionaryDir(commonFactory: CommonFactory, configPath: Path) { - CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.forEach { (name, dictionaryDirMetadata) -> - assertEquals( - configPath.resolve(dictionaryDirMetadata.dirName), - dictionaryDirMetadata.getPath.invoke(commonFactory), - "Configured config path: $configPath, config name: $name" - ) + // 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 assertConfigs(commonFactory: CommonFactory) { + 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, customPaths: Boolean) { - val provider = assertProvider(commonFactory) - assertEquals(configPath, provider.baseDir) - - if (customPaths) { - assertEquals(CONFIG_ALIASES.size, provider.customPaths.size) - CONFIG_ALIASES.keys.forEach { alias -> - val path = assertNotNull(provider.customPaths[alias]) - assertEquals(configPath, path.parent, "Configured config path: $configPath") + private fun assertConfigs(commonFactory: CommonFactory, configPath: Path) { + assertAll( + ConfigHelper.values().map { helper -> + { + val cfgBean = helper.writeConfigByDefaultPath(configPath) + assertThat(helper.loadConfig(commonFactory), samePropertyValuesAs(cfgBean)) + } } - } else { - assertEquals(0, provider.customPaths.size) - } + ) } private fun assertProvider(commonFactory: CommonFactory): JsonConfigurationProvider { @@ -370,57 +398,175 @@ class CommonFactoryTest { return JsonConfigurationProvider::class.cast(commonFactory.getConfigurationProvider()) } - private fun assertCustomPathForConfig(commonFactory: CommonFactory, basePath: Path, alias: String) { - val provider = assertProvider(commonFactory) - assertEquals(basePath, provider.baseDir) - assertEquals(1, provider.customPaths.size) - assertEquals(CUSTOM_DIR, provider.customPaths[alias]) - } - - private fun FactorySettings.setCustomPathForConfig(alias: String) { - CONFIG_ALIASES.asSequence() - .find { (optionName, _) -> optionName == alias } - ?.value?.setPath?.invoke(this, CUSTOM_DIR) - } + data class TestCustomConfig(val testField: String = "test-value") - private fun assertCustomDictionaryPath( - name: String, - basePath: Path, - commonFactory: CommonFactory + enum class ConfigHelper( + val alias: String, + private val configClass: Class<*> ) { - CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.forEach { (optionName, dictionaryDirMetadata) -> - if (optionName == name) { - assertEquals( - CUSTOM_DIR, - dictionaryDirMetadata.getPath.invoke(commonFactory), - "Configured config path: $CUSTOM_DIR, config optionName: $optionName" - ) - } else { - assertEquals( - basePath.resolve(dictionaryDirMetadata.dirName), - dictionaryDirMetadata.getPath.invoke(commonFactory), - "Configured config path: $basePath, config optionName: $optionName" - ) + RABBIT_MQ_CFG_ALIAS("rabbitMQ", RabbitMQConfiguration::class.java) { + override fun setOption(settings: FactorySettings, filePath: Path) { + settings.rabbitMQ = filePath } - } - } - private fun FactorySettings.setCustomDictionaryPath( - name: String - ) { - CONFIG_NAME_TO_COMMON_FACTORY_SUPPLIER.asSequence() - .find { (optionName, _) -> optionName == name } - ?.value?.setPath?.invoke(this, CUSTOM_DIR) + 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) } - private data class DictionaryDirMetadata( - val dirName: String, - val setPath: FactorySettings.(Path) -> Unit, - val getPath: CommonFactory.() -> Path, - ) + 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 data class ConfigMetadata( - val setPath: FactorySettings.(Path) -> Unit, - ) + 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 From 558295889437d53d36672451be51d44da095ee98 Mon Sep 17 00:00:00 2001 From: "nikita.smirnov" Date: Wed, 2 Apr 2025 19:11:13 +0400 Subject: [PATCH 10/10] Corrected after review --- .../common/schema/factory/CommonFactory.java | 17 ++++++----------- .../schema/configuration/IDictionaryProvider.kt | 2 +- .../configuration/impl/DictionaryProvider.kt | 9 ++++++++- .../impl/JsonConfigurationProvider.kt | 13 +++++++++++-- 4 files changed, 26 insertions(+), 15 deletions(-) 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 16f2f189..2f9a8051 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 @@ -68,14 +68,9 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; -import static java.util.Objects.requireNonNullElseGet; -import static org.apache.commons.io.FilenameUtils.removeExtension; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; /** @@ -116,13 +111,13 @@ public class CommonFactory extends AbstractCommonFactory { private static final String CRADLE_MANAGER_CONFIG_MAP = "cradle-manager"; private static final String LOGGING_CONFIG_MAP = "logging-config"; - final IConfigurationProvider configurationProvider; - final IDictionaryProvider dictionaryProvider; - final ConfigurationManager configurationManager; + private final IConfigurationProvider configurationProvider; + private final IDictionaryProvider dictionaryProvider; + private final ConfigurationManager configurationManager; private static final Logger LOGGER = LoggerFactory.getLogger(CommonFactory.class.getName()); - private CommonFactory(@NotNull IConfigurationProvider configurationProvider, @NotNull IDictionaryProvider dictionaryProvider) { + public 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"); @@ -561,7 +556,7 @@ private static IDictionaryProvider createDictionaryProvider(FactorySettings sett 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); + return DictionaryProvider.create(defaultIfNull(settings.getBaseConfigDir(), getConfigPath()), paths); } private static IConfigurationProvider createConfigurationProvider(FactorySettings settings) { @@ -576,7 +571,7 @@ private static IConfigurationProvider createConfigurationProvider(FactorySetting 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); + return JsonConfigurationProvider.create(defaultIfNull(settings.getBaseConfigDir(), getConfigPath()), paths); } private static ConfigurationManager createConfigurationManager(IConfigurationProvider configurationProvider) { Map, String> paths = new HashMap<>(); 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 index 3121b9c2..e17de4d2 100644 --- a/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt +++ b/src/main/kotlin/com/exactpro/th2/common/schema/configuration/IDictionaryProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 Exactpro (Exactpro Systems Limited) + * Copyright 2024-2025 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 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 index 3ccafdfe..22be51eb 100644 --- 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 @@ -32,7 +32,7 @@ import kotlin.io.path.isDirectory import kotlin.io.path.isRegularFile import kotlin.streams.asSequence -class DictionaryProvider @JvmOverloads constructor( +class DictionaryProvider private constructor( baseDir: Path, paths: Map = emptyMap() ) : IDictionaryProvider { @@ -209,6 +209,13 @@ class DictionaryProvider @JvmOverloads constructor( companion object { private fun toAlias(path: Path) = FilenameUtils.removeExtension(path.fileName.toString()) + + @JvmStatic + @JvmOverloads + fun create( + baseDir: Path, + paths: Map = emptyMap() + ): DictionaryProvider = DictionaryProvider(baseDir, paths) } } 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 index defe75a8..831daac6 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023 Exactpro (Exactpro Systems Limited) + * Copyright 2023-2025 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 @@ -32,7 +32,7 @@ import java.util.function.Supplier import kotlin.io.path.exists -class JsonConfigurationProvider @JvmOverloads constructor( +class JsonConfigurationProvider private constructor( internal val baseDir: Path, internal val customPaths: Map = emptyMap(), ) : IConfigurationProvider { @@ -91,6 +91,8 @@ class JsonConfigurationProvider @JvmOverloads constructor( private const val EXTENSION = "json" private val K_LOGGER = KotlinLogging.logger {} + + @Suppress("SpellCheckingInspection") private val SUBSTITUTOR: ThreadLocal = object : ThreadLocal() { override fun initialValue(): StringSubstitutor = StringSubstitutor(System.getenv()) } @@ -110,5 +112,12 @@ class JsonConfigurationProvider @JvmOverloads constructor( JavaTimeModule() ) } + + @JvmStatic + @JvmOverloads + fun create( + baseDir: Path, + customPaths: Map = emptyMap() + ): JsonConfigurationProvider = JsonConfigurationProvider(baseDir, customPaths) } } \ No newline at end of file