diff --git a/README.md b/README.md index 16d37cb6a..240334c4d 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 4cc7e554f..3b92c28a0 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 000000000..0978c9d25 --- /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 000000000..f06109ab5 --- /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