diff --git a/.github/workflows/dev-release-java-publish-sonatype-and-docker.yml b/.github/workflows/dev-release-java-publish-sonatype-and-docker.yml new file mode 100644 index 0000000..d2acfe9 --- /dev/null +++ b/.github/workflows/dev-release-java-publish-sonatype-and-docker.yml @@ -0,0 +1,80 @@ +name: Build and publish Docker distributions to Github Container Registry ghcr.io + +on: + push: + tags: + - \d+.\d+.\d+-dev + +jobs: + grpc-version: + name: Extract grpc version + uses: th2-net/.github/.github/workflows/compound-prebuild-java-dev-workflow.yml@main + with: + project-path: grpc + app-version: + name: Extract app version + uses: th2-net/.github/.github/workflows/compound-prebuild-java-dev-workflow.yml@main + with: + project-path: app + build-and-publish-grpc-java: + name: Build and publish Java gRPC distributions + runs-on: 'ubuntu-20.04' + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 'zulu' '11' + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + - name: Build with Gradle + run: ./gradlew --info -Pversion_suffix='dev' clean build publish closeAndReleaseSonatypeStagingRepository + env: + ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_NEXUS_PASSWORD }} + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SONATYPE_GPG_ARMORED_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SONATYPE_SIGNING_PASSWORD }} + + build-and-publish-grpc-python: + name: Build and publish Python distributions to PyPI + needs: + - build-and-publish-grpc-java + - grpc-version + uses: th2-net/.github/.github/workflows/compaund-python-grpc-pypi-publication.yml@main + with: + custom-version: ${{ needs.grpc-version.outputs.version }}rc1 + project-path: grpc + secrets: + pypi_password: ${{ secrets.PYPI_PASSWORD }} + + owasp-scan: + uses: th2-net/.github/.github/workflows/owasp-gradle-scan.yml@main + with: + multiproject: true + + publish-docker: + name: Build and publish docker image + needs: + - build-and-publish-grpc-python + - build-and-publish-grpc-java + - app-version + uses: th2-net/.github/.github/workflows/compaund-java-docker-push.yml@main + with: + docker-username: ${{ github.actor }} + version: ${{ needs.app-version.outputs.version }}-dev + versionNumber: ${{ needs.app-version.outputs.version }} + dockerContext: '.' + gradleParameters: '["clean", "build", "dockerPrepare"]' + secrets: + docker-password: ${{ secrets.GITHUB_TOKEN } + trivy-docker-scan: + name: Scan Docker image for vulnerabilities + needs: + - publish-docker + - app-version + uses: th2-net/.github/.github/workflows/trivy-scan-github.yml@main + with: + image-path: ghcr.io/${{ github.repository }}:${{ needs.app-version.outputs.version }}-dev + target: 'os,library' + sensitivity: 'CRITICAL,HIGH,MEDIUM' \ No newline at end of file diff --git a/README.md b/README.md index fc0b691..b9c1223 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# th2-read-db +# th2-read-db 0.3.0 + The read-db is a component for extracting data from databases using JDBC technology. If database has JDBC driver the read can work with the database # Configuration @@ -150,6 +151,7 @@ spec: queueSize: 1000 maxDelayMillis: 1000 maxBatchSize: 100 + useTransport: true pins: - name: server connection-type: grpc-server @@ -158,7 +160,7 @@ spec: - th2.read_db.ReadDbService - name: store connection-type: mq - attributes: ['raw', 'publish', 'store'] + attributes: ['transport-group', 'publish', 'store'] extended-settings: service: enabled: false @@ -171,4 +173,14 @@ spec: requests: memory: 100Mi cpu: 50m -``` \ No newline at end of file +``` + +## Changes + +### 0.3.0 + ++ MSSQL support added + +### 0.2.0 + ++ Added support for th2 transport protocol \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 38b698c..63e51f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,15 +18,18 @@ dependencies { implementation project(':read-db-core') //region Drivers - runtimeOnly('org.postgresql:postgresql:42.5.3') { + runtimeOnly('org.postgresql:postgresql:42.6.0') { because('prostresql support') } - runtimeOnly('com.mysql:mysql-connector-j:8.0.32') { + runtimeOnly('com.mysql:mysql-connector-j:8.1.0') { because('mysql support') } - runtimeOnly('com.oracle.database.jdbc:ojdbc11:21.9.0.0') { + runtimeOnly('com.oracle.database.jdbc:ojdbc11:23.2.0.0') { because('oracle support') } + runtimeOnly('com.microsoft.sqlserver:mssql-jdbc:12.4.0.jre11') { + because('mssql support') + } //endregion } diff --git a/app/gradle.properties b/app/gradle.properties index 956846d..13f27c9 100644 --- a/app/gradle.properties +++ b/app/gradle.properties @@ -1,2 +1,2 @@ kotlin.code.style=official -release_version=0.1.0 \ No newline at end of file +release_version=0.3.0 \ No newline at end of file diff --git a/build.gradle b/build.gradle index 62963dd..2f37988 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,13 @@ +import com.github.jk1.license.filter.LicenseBundleNormalizer +import com.github.jk1.license.render.JsonReportRenderer + plugins { - id "io.github.gradle-nexus.publish-plugin" version "1.1.0" - id 'org.jetbrains.kotlin.jvm' version '1.6.21' apply false - id "org.owasp.dependencycheck" version "8.1.2" + id "io.github.gradle-nexus.publish-plugin" version "1.3.0" + id 'org.jetbrains.kotlin.jvm' version '1.8.22' apply false + id "org.owasp.dependencycheck" version "8.4.0" + id "com.gorylenko.gradle-git-properties" version "2.4.1" + id "com.github.jk1.dependency-license-report" version "2.5" + id "de.undercouch.download" version "5.5.0" } allprojects { @@ -31,4 +37,20 @@ dependencyCheck { dependencyLocking { lockAllConfigurations() +} + +licenseReport { + def licenseNormalizerBundlePath = "$buildDir/license-normalizer-bundle.json" + + if (!file(licenseNormalizerBundlePath).exists()) { + download.run { + src 'https://raw.githubusercontent.com/th2-net/.github/main/license-compliance/gradle-license-report/license-normalizer-bundle.json' + dest "$buildDir/license-normalizer-bundle.json" + overwrite false + } + } + + filters = [new LicenseBundleNormalizer(licenseNormalizerBundlePath, false)] + renderers = [new JsonReportRenderer('licenses.json', false)] + allowedLicensesFile = new URL("https://raw.githubusercontent.com/th2-net/.github/main/license-compliance/gradle-license-report/allowed-licenses.json") } \ No newline at end of file diff --git a/common.gradle b/common.gradle index 08d4769..8ec08f1 100644 --- a/common.gradle +++ b/common.gradle @@ -1,5 +1,5 @@ dependencies { - implementation platform('com.exactpro.th2:bom:4.2.0') + api platform('com.exactpro.th2:bom:4.5.0') } test { diff --git a/core/build.gradle b/core/build.gradle index 6465715..c591836 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,5 +1,11 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import com.github.jk1.license.filter.LicenseBundleNormalizer +import com.github.jk1.license.render.JsonReportRenderer + plugins { - id 'org.jetbrains.kotlin.jvm' + id "org.jetbrains.kotlin.jvm" + id "com.github.jk1.dependency-license-report" version "2.5" + id "de.undercouch.download" version "5.5.0" } apply from: '../common.gradle' @@ -9,10 +15,10 @@ sourceCompatibility = 11 targetCompatibility = 11 ext { - coroutines_version = '1.6.4' + coroutines_version = '1.7.3' } -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { +tasks.withType(KotlinCompile).configureEach { kotlinOptions { jvmTarget = "11" freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn' @@ -25,33 +31,36 @@ configurations.configureEach { } dependencies { - implementation project(':grpc-read-db') + implementation project(":grpc-read-db") - implementation 'com.exactpro.th2:common:5.2.0-dev' - implementation 'org.slf4j:slf4j-api' + api platform("com.exactpro.th2:bom:4.5.0") + implementation "com.exactpro.th2:common:5.4.0-dev" + implementation "com.exactpro.th2:common-utils:2.2.0-dev" + implementation "org.slf4j:slf4j-api" - implementation('org.apache.commons:commons-dbcp2:2.9.0') { - because('connection pool') + implementation("org.apache.commons:commons-dbcp2:2.9.0") { + because("connection pool") } - implementation 'org.apache.commons:commons-text:1.9' - implementation('com.opencsv:opencsv:5.6') { - because('publishes raw messages in csv format') + implementation "org.apache.commons:commons-text" + implementation("com.opencsv:opencsv:5.8") { + because("publishes raw messages in csv format") } implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" - implementation 'io.github.microutils:kotlin-logging:3.0.0' + implementation "io.github.microutils:kotlin-logging:3.0.5" + implementation "com.fasterxml.jackson.core:jackson-databind" - testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' + testImplementation "org.junit.jupiter:junit-jupiter:5.10.0" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version" - testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" + testImplementation "org.mockito.kotlin:mockito-kotlin:5.1.0" testImplementation "io.strikt:strikt-core:0.34.1" - testImplementation platform('org.testcontainers:testcontainers-bom:1.17.3') - testImplementation 'org.testcontainers:testcontainers' - testImplementation 'org.testcontainers:mysql' + testImplementation platform("org.testcontainers:testcontainers-bom:1.19.0") + testImplementation "org.testcontainers:testcontainers" + testImplementation "org.testcontainers:mysql" - testRuntimeOnly('mysql:mysql-connector-java:8.0.30') { - because('mysql support') + testRuntimeOnly("com.mysql:mysql-connector-j:8.1.0") { + because("mysql support") } } @@ -87,4 +96,20 @@ jar { 'Implementation-Version': project.version ) } +} + +licenseReport { + def licenseNormalizerBundlePath = "$buildDir/license-normalizer-bundle.json" + + if (!file(licenseNormalizerBundlePath).exists()) { + download.run { + src 'https://raw.githubusercontent.com/th2-net/.github/main/license-compliance/gradle-license-report/license-normalizer-bundle.json' + dest "$buildDir/license-normalizer-bundle.json" + overwrite false + } + } + + filters = [new LicenseBundleNormalizer(licenseNormalizerBundlePath, false)] + renderers = [new JsonReportRenderer('licenses.json', false)] + allowedLicensesFile = new URL("https://raw.githubusercontent.com/th2-net/.github/main/license-compliance/gradle-license-report/allowed-licenses.json") } \ No newline at end of file diff --git a/core/gradle.properties b/core/gradle.properties index e5bf313..13f27c9 100644 --- a/core/gradle.properties +++ b/core/gradle.properties @@ -1,2 +1,2 @@ kotlin.code.style=official -release_version=0.0.1 \ No newline at end of file +release_version=0.3.0 \ No newline at end of file diff --git a/core/src/main/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderConfiguration.kt b/core/src/main/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderConfiguration.kt index 5167ad6..3aed499 100644 --- a/core/src/main/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderConfiguration.kt +++ b/core/src/main/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Exactpro (Exactpro Systems Limited) + * Copyright 2022-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. @@ -28,7 +28,8 @@ class DataBaseReaderConfiguration( val dataSources: Map, val queries: Map, val startupTasks: List = emptyList(), - val publication: PublicationConfiguration = PublicationConfiguration() + val publication: PublicationConfiguration = PublicationConfiguration(), + val useTransport: Boolean = false ) class PublicationConfiguration( diff --git a/core/src/main/kotlin/com/exactpro/th2/read/db/bootstrap/Main.kt b/core/src/main/kotlin/com/exactpro/th2/read/db/bootstrap/Main.kt index 50c36ba..25c24ce 100644 --- a/core/src/main/kotlin/com/exactpro/th2/read/db/bootstrap/Main.kt +++ b/core/src/main/kotlin/com/exactpro/th2/read/db/bootstrap/Main.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Exactpro (Exactpro Systems Limited) + * Copyright 2022-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. @@ -17,12 +17,13 @@ @file:JvmName("Main") package com.exactpro.th2.read.db.bootstrap -import com.exactpro.th2.common.grpc.Direction -import com.exactpro.th2.common.grpc.MessageGroupBatch -import com.exactpro.th2.common.grpc.RawMessage +import com.exactpro.th2.common.grpc.Direction as ProtoDirection +import com.exactpro.th2.common.grpc.MessageGroupBatch as ProtoMessageGroupBatch +import com.exactpro.th2.common.grpc.RawMessage as ProtoRawMessage import com.exactpro.th2.common.message.direction import com.exactpro.th2.common.message.plusAssign import com.exactpro.th2.common.message.sequence +import com.exactpro.th2.common.message.sessionGroup import com.exactpro.th2.common.message.sessionAlias import com.exactpro.th2.common.message.toTimestamp import com.exactpro.th2.common.metrics.LIVENESS_MONITOR @@ -31,6 +32,8 @@ import com.exactpro.th2.common.schema.factory.CommonFactory import com.exactpro.th2.common.schema.factory.extensions.getCustomConfiguration import com.exactpro.th2.common.schema.message.MessageRouter import com.exactpro.th2.common.schema.message.QueueAttribute +import com.exactpro.th2.common.schema.message.impl.rabbitmq.transport.* +import com.exactpro.th2.common.utils.message.transport.toGroup import com.exactpro.th2.read.db.app.DataBaseReader import com.exactpro.th2.read.db.app.DataBaseReaderConfiguration import com.exactpro.th2.read.db.app.validate @@ -41,6 +44,7 @@ import com.exactpro.th2.read.db.impl.grpc.DataBaseReaderGrpcServer import com.google.common.util.concurrent.ThreadFactoryBuilder import com.google.protobuf.UnsafeByteOperations import com.opencsv.CSVWriterBuilder +import io.netty.buffer.Unpooled import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -122,11 +126,48 @@ internal fun setupApp( // The BOX is alive LIVENESS_MONITOR.enable() - val messageQueue: BlockingQueue = configureMessageStoring(factory, cfg, closeResource) - val appScope = createScope(closeResource) - - val reader = createReader(cfg, appScope, messageQueue, closeResource) + val componentBookName = factory.boxConfiguration.bookName + val reader = if (cfg.useTransport) { + val messageRouter: MessageRouter = factory.transportGroupBatchRouter + val messageQueue: BlockingQueue = configureTransportMessageStoring( + cfg, + ::transportKeyExtractor, + TransportPreprocessor(componentBookName), + closeResource + ) { + val sessionAlias = it[0].idBuilder().sessionAlias + messageRouter.sendAll( + GroupBatch( + componentBookName, + sessionAlias, + it.map { builder -> builder.build().toGroup() } + ), + QueueAttribute.TRANSPORT_GROUP.value + ) + } + createReader(cfg, appScope, messageQueue, closeResource, TableRow::toTransportMessage) + } else { + val messageRouter = factory.messageRouterMessageGroupBatch + val messageQueue = configureTransportMessageStoring( + cfg, + ::protoKeyExtractor, + ProtoPreprocessor(componentBookName), + closeResource + ) { + messageRouter.sendAll( + ProtoMessageGroupBatch.newBuilder() + .apply { + it.forEach { + addGroupsBuilder() += it + } + } + .build(), + QueueAttribute.RAW.value, + ) + } + createReader(cfg, appScope, messageQueue, closeResource, TableRow::toProtoMessage) + } val handler = DataBaseReaderGrpcServer(reader) @@ -146,11 +187,12 @@ internal fun setupApp( READINESS_MONITOR.enable() } -private fun createReader( +private fun createReader( cfg: DataBaseReaderConfiguration, appScope: CoroutineScope, - messageQueue: BlockingQueue, + messageQueue: BlockingQueue, closeResource: (name: String, resource: () -> Unit) -> Unit, + toMessage: TableRow.(DataSourceId) -> BUILDER ): DataBaseReader { val reader = DataBaseReader.createDataBaseReader( cfg, @@ -179,18 +221,34 @@ private fun createReader( return reader } -private fun TableRow.toMessage(dataSourceId: DataSourceId): RawMessage.Builder { - return RawMessage.newBuilder() +private fun TableRow.toProtoMessage(dataSourceId: DataSourceId): ProtoRawMessage.Builder { + return ProtoRawMessage.newBuilder() .setBody(UnsafeByteOperations.unsafeWrap(toCsvBody())) .apply { sessionAlias = dataSourceId.id - direction = Direction.FIRST + direction = ProtoDirection.FIRST associatedMessageType?.also { metadataBuilder.putProperties("th2.csv.override_message_type", it) } } } +private fun TableRow.toTransportMessage(dataSourceId: DataSourceId): RawMessage.Builder { + val builder = RawMessage.builder() + .setBody(Unpooled.wrappedBuffer(toCsvBody())) + .apply { + idBuilder() + .setSessionAlias(dataSourceId.id) + .setDirection(Direction.INCOMING) + } + + if (associatedMessageType != null) { + builder.setMetadata(mapOf("th2.csv.override_message_type" to associatedMessageType)) + } + + return builder +} + private fun TableRow.toCsvBody(): ByteArray { return ByteArrayOutputStream().use { CSVWriterBuilder(it.writer()) @@ -223,62 +281,33 @@ private fun createScope(closeResource: (name: String, resource: () -> Unit) -> U return appScope } -private fun configureMessageStoring( - factory: CommonFactory, +private fun configureTransportMessageStoring( cfg: DataBaseReaderConfiguration, + keyExtractor: (BUILDER) -> SessionKey, + preprocessor: Preprocessor, closeResource: (name: String, resource: () -> Unit) -> Unit, -): BlockingQueue { - val messageRouter: MessageRouter = factory.messageRouterMessageGroupBatch - - val messagesQueue: BlockingQueue = ArrayBlockingQueue(cfg.publication.queueSize) + send: (List) -> Unit +): BlockingQueue { + val messagesQueue: BlockingQueue = ArrayBlockingQueue(cfg.publication.queueSize) val executor = Executors.newSingleThreadExecutor( ThreadFactoryBuilder() - .setNameFormat("message-saver-%d") + .setNameFormat("transport-message-saver-%d") .build() ) - val sequences = ConcurrentHashMap() - - val nanosInSecond = TimeUnit.SECONDS.toNanos(1) - val running = AtomicBoolean(true) - val componentBookName = factory.boxConfiguration.bookName val drainFuture = executor.submit(Saver( messagesQueue, running, cfg.publication.maxBatchSize, cfg.publication.maxDelayMillis, - { it.run { SessionKey(sessionAlias, direction) } }, - { key, builder -> - builder.apply { - sequence = sequences.compute(key) { _, prev -> - if (prev == null) { - Instant.now().run { epochSecond * nanosInSecond + nano } - } else { - prev + 1 - } - }.let(::requireNotNull) - metadataBuilder.idBuilder.apply { - timestamp = Instant.now().toTimestamp() - bookName = componentBookName - } - } - } - ) { messages -> - messageRouter.sendAll( - MessageGroupBatch.newBuilder() - .apply { - messages.forEach { - addGroupsBuilder() += it - } - } - .build(), - QueueAttribute.RAW.value, - ) - }) + keyExtractor, + preprocessor::preprocess, + send + )) - closeResource("message storing") { + closeResource("transport message storing") { if (running.compareAndSet(true, false)) { try { drainFuture.get(1, TimeUnit.MINUTES) @@ -297,7 +326,56 @@ private fun configureMessageStoring( return messagesQueue } -private data class SessionKey(val alias: String, val direction: Direction) +private val nanosInSecond = TimeUnit.SECONDS.toNanos(1) + +private fun protoKeyExtractor(builder: ProtoRawMessage.Builder): SessionKey = + SessionKey(builder.sessionAlias, builder.direction) + +private fun transportKeyExtractor(builder: RawMessage.Builder): SessionKey = + SessionKey(builder.idBuilder().sessionAlias, builder.idBuilder().direction) + +private abstract class Preprocessor(protected val configBookName: String) { + protected val sequences = ConcurrentHashMap, Long>() + abstract fun preprocess(key: SessionKey, builder: BUILDER): BUILDER +} + +private class ProtoPreprocessor(bookName: String) : Preprocessor(bookName) { + override fun preprocess(key: SessionKey, builder: ProtoRawMessage.Builder): ProtoRawMessage.Builder = + builder.apply { + sequence = sequences.compute(key) { _, prev -> + if (prev == null) { + Instant.now().run { epochSecond * nanosInSecond + nano } + } else { + prev + 1 + } + }.let(::requireNotNull) + metadataBuilder.idBuilder.apply { + timestamp = Instant.now().toTimestamp() + sessionGroup = sessionAlias + bookName = configBookName + } + } +} + +private class TransportPreprocessor(bookName: String) : Preprocessor(bookName) { + override fun preprocess(key: SessionKey, builder: RawMessage.Builder): RawMessage.Builder { + builder.idBuilder().apply { + setSequence(requireNotNull( + sequences.compute(key) { _, prev -> + if (prev == null) { + Instant.now().run { epochSecond * nanosInSecond + nano } + } else { + prev + 1 + } + } + )) + setTimestamp(Instant.now()) + } + return builder + } +} + +private data class SessionKey(val alias: String, val direction: DIRECTION) private fun configureShutdownHook(resources: Deque<() -> Unit>, lock: ReentrantLock, condition: Condition) { Runtime.getRuntime().addShutdownHook(thread( diff --git a/core/src/test/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderIntegrationTest.kt b/core/src/test/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderIntegrationTest.kt index 7e70176..dc82e55 100644 --- a/core/src/test/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderIntegrationTest.kt +++ b/core/src/test/kotlin/com/exactpro/th2/read/db/app/DataBaseReaderIntegrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Exactpro (Exactpro Systems Limited) + * Copyright 2022-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. @@ -26,17 +26,16 @@ import com.exactpro.th2.read.db.core.ResultListener import com.exactpro.th2.read.db.core.RowListener import com.exactpro.th2.read.db.core.TableRow import com.exactpro.th2.read.db.core.UpdateListener -import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.argumentCaptor -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.times -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyZeroInteractions +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.advanceUntilIdle -import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.runTest import mu.KotlinLogging import org.junit.jupiter.api.AfterAll @@ -124,7 +123,7 @@ internal class DataBaseReaderIntegrationTest { genericRowListener.assertCaptured(persons) listener.assertCaptured(persons) - verifyZeroInteractions(genericUpdateListener) + verifyNoInteractions(genericUpdateListener) } } @@ -180,7 +179,7 @@ internal class DataBaseReaderIntegrationTest { genericUpdateListener.assertCaptured(newData) listener.assertCaptured(newData) - verifyZeroInteractions(genericRowListener) + verifyNoInteractions(genericRowListener) reader.stopPullTask(taskId) advanceUntilIdle() diff --git a/core/src/test/kotlin/com/exactpro/th2/read/db/app/TestDataBaseReaderConfiguration.kt b/core/src/test/kotlin/com/exactpro/th2/read/db/app/TestDataBaseReaderConfiguration.kt index 88a9a0f..2a10b58 100644 --- a/core/src/test/kotlin/com/exactpro/th2/read/db/app/TestDataBaseReaderConfiguration.kt +++ b/core/src/test/kotlin/com/exactpro/th2/read/db/app/TestDataBaseReaderConfiguration.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 Exactpro (Exactpro Systems Limited) + * Copyright 2022-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. diff --git a/gradle.properties b/gradle.properties index 805ddb5..9d911dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -vcs_url=https://github.com/th2-net/th2-grpc-read-db +vcs_url=https://github.com/th2-net/th2-read-db release_version=0.0.0 version_suffix= \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..070cb70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/grpc/build.gradle b/grpc/build.gradle index 53fed08..623bfcd 100644 --- a/grpc/build.gradle +++ b/grpc/build.gradle @@ -1,9 +1,14 @@ +import com.github.jk1.license.filter.LicenseBundleNormalizer +import com.github.jk1.license.render.JsonReportRenderer + plugins { id 'java' id 'java-library' id 'maven-publish' id 'signing' - id "com.google.protobuf" version "0.9.2" + id "com.google.protobuf" version "0.9.3" + id "com.github.jk1.dependency-license-report" version "2.5" + id "de.undercouch.download" version "5.5.0" } apply from: '../common.gradle' @@ -13,9 +18,11 @@ sourceCompatibility = 11 targetCompatibility = 11 ext { - grpcVersion = '1.48.1' - protobufVersion = '3.21.12' - serviceGeneratorVersion = '3.3.0' + grpcVersion = '1.58.0' + protobufVersion = '3.24.3' // The protoc:3.23.3 https://github.com/protocolbuffers/protobuf/issues/13070 + serviceGeneratorVersion = '3.4.0' + grpcCommonVersion = '4.3.0-dev' + genBaseDir = file("${buildDir}/generated/source/proto") // from protobuf plugin } @@ -33,7 +40,8 @@ dependencies { implementation "javax.annotation:javax.annotation-api:1.3.2" - implementation "com.exactpro.th2:grpc-service-generator:$serviceGeneratorVersion" + api "com.exactpro.th2:grpc-common:$grpcCommonVersion" + api "com.exactpro.th2:grpc-service-generator:$serviceGeneratorVersion" } sourceSets { @@ -50,36 +58,50 @@ sourceSets { protobuf { protoc { - artifact = "com.google.protobuf:protoc:${protobufVersion}" + artifact = "com.google.protobuf:protoc:$protobufVersion" } plugins { grpc { - artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + artifact = "io.grpc:protoc-gen-grpc-java:$grpcVersion" } services { - artifact = "com.exactpro.th2:grpc-service-generator:${serviceGeneratorVersion}:all@jar" + artifact = "com.exactpro.th2:grpc-service-generator:$serviceGeneratorVersion:all@jar" } } generateProtoTasks { - ofSourceSet('main').configureEach { - plugins { - grpc {} - services { - option 'javaInterfacesPath=./java' - option 'javaInterfacesImplPath=./java' - option 'javaMetaInfPath=./java/resources' - option 'pythonPath=./python' - } + all()*.plugins { + grpc {} + services { + option 'javaInterfacesPath=./java/src' + option 'javaInterfacesImplPath=./java/src' + option 'javaMetaInfPath=./java/resources' + option 'pythonPath=./python' } } + ofSourceSet('main') } } - -compileJava.dependsOn('generateProto') -processResources.dependsOn('generateProto') +compileJava.dependsOn.add('generateProto') +processResources.dependsOn.add('generateProto') java { withJavadocJar() withSourcesJar() +} + +licenseReport { + def licenseNormalizerBundlePath = "$buildDir/license-normalizer-bundle.json" + + if (!file(licenseNormalizerBundlePath).exists()) { + download.run { + src 'https://raw.githubusercontent.com/th2-net/.github/main/license-compliance/gradle-license-report/license-normalizer-bundle.json' + dest "$buildDir/license-normalizer-bundle.json" + overwrite false + } + } + + filters = [new LicenseBundleNormalizer(licenseNormalizerBundlePath, false)] + renderers = [new JsonReportRenderer('licenses.json', false)] + allowedLicensesFile = new URL("https://raw.githubusercontent.com/th2-net/.github/main/license-compliance/gradle-license-report/allowed-licenses.json") } \ No newline at end of file