Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# th2 common library (Java) (5.4.2)
# th2 common library (Java) (5.6.0)

## Usage

Expand Down Expand Up @@ -491,6 +491,11 @@ dependencies {

## Release notes

### 5.6.0-dev
#### Feature:
+ Added common microservice entry point
+ Added configuration provider to common factory

### 5.5.0-dev

#### Changed:
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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() {}
}
151 changes: 151 additions & 0 deletions src/main/kotlin/com/exactpro/th2/common/microservice/Main.kt
Original file line number Diff line number Diff line change
@@ -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<String>) {

private val liveness: MetricMonitor = registerLiveness(MONITOR_NAME)
private val readiness: MetricMonitor = registerReadiness(MONITOR_NAME)

private val resources: Deque<AutoCloseable> = 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<String>) {
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<AutoCloseable>,
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" }
}
}
Original file line number Diff line number Diff line change
@@ -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 <T : Any> load(configClass: Class<T>, alias: String, default: () -> T): T
}
Loading