diff --git a/README.md b/README.md index ebae1f7..b2a6ef5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Kotlin Commons +# Kotlin/JVM Commons [![Apache License 2](https://img.shields.io/badge/license-ASF2-purple.svg)](https://www.apache.org/licenses/LICENSE-2.0.txt) [![](https://jitpack.io/v/sokomishalov/commons.svg)](https://jitpack.io/#sokomishalov/commons) @@ -28,4 +28,10 @@ Add the dependency: Available modules now are: - core +- logging +- serialization +- reactor +- coroutines +- distributed-locks +- cache - spring diff --git a/commons-cache/pom.xml b/commons-cache/pom.xml new file mode 100644 index 0000000..aee0dc1 --- /dev/null +++ b/commons-cache/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + + ru.sokomishalov.commons + commons-parent + 1.1.0 + + + commons-cache + 1.1.0 + + + + + + + ru.sokomishalov.commons + commons-core + 1.1.0 + + + + ru.sokomishalov.commons + commons-logging + 1.1.0 + + + + ru.sokomishalov.commons + commons-serialization + 1.1.0 + + + + ru.sokomishalov.commons + commons-coroutines + 1.1.0 + + + + + + org.aspectj + aspectjweaver + ${aspectj.version} + true + + + + org.mongodb + mongodb-driver-reactivestreams + ${mongo-reactive.version} + true + + + + io.lettuce + lettuce-core + ${lettuce.version} + true + + + + org.springframework + spring-context + ${spring.version} + + + + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + + org.testcontainers + testcontainers + ${test-containers.version} + test + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + ${project.artifactId} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + -Xuse-experimental=kotlin.Experimental + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + kapt + + kapt + + + + ${project.basedir}/src/main/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + + + + \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/CacheService.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/CacheService.kt new file mode 100644 index 0000000..7038193 --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/CacheService.kt @@ -0,0 +1,101 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("unused") + +package ru.sokomishalov.commons.cache + +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.JavaType +import com.fasterxml.jackson.databind.ObjectMapper +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER +import java.time.Duration +import java.time.Duration.ofSeconds +import java.time.Period +import java.time.temporal.TemporalAmount + +/** + * @author sokomishalov + */ +interface CacheService { + + // --------------------------------------------------------------------------------------------------------------------------------- + + val mapper: ObjectMapper get() = OBJECT_MAPPER + val cacheName: String get() = "cache" + + // --------------------------------------------------------------------------------------------------------------------------------- + + suspend fun getRaw(key: String): ByteArray? + suspend fun putRaw(key: String, value: ByteArray) + suspend fun expire(key: String, ttl: TemporalAmount) + suspend fun delete(key: String) + suspend fun findKeys(glob: String): List + + // --------------------------------------------------------------------------------------------------------------------------------- + + suspend fun exists(key: String): Boolean { + return getRaw(key) != null + } + + suspend fun getOne(key: String, clazz: Class): T? { + return getRaw(key)?.deserializeValue(clazz) + } + + suspend fun getList(key: String, clazz: Class): List { + val collectionType = mapper.typeFactory.constructCollectionType(List::class.java, clazz) + return getRaw(key)?.deserializeValue(collectionType) ?: emptyList() + } + + suspend fun getMap(key: String, clazz: Class): Map { + val mapType = mapper.typeFactory.constructMapType(HashMap::class.java, String::class.java, clazz) + return getRaw(key)?.deserializeValue(mapType) ?: emptyMap() + } + + suspend fun getFromMap(key: String, mapKey: String, clazz: Class): T? { + return getMap(key, clazz)[mapKey] + } + + suspend fun put(key: String, value: T, ttl: TemporalAmount = ofSeconds(-1)) { + putRaw(key, value.serializeValue()) + when { + ttl is Duration && ttl.isNegative.not() -> expire(key, ttl) + ttl is Period && ttl.isNegative.not() -> expire(key, ttl) + } + } + + suspend fun findAllKeys(): List { + return findKeys("*") + } + + suspend fun find(pattern: String, clazz: Class): List { + return findKeys(pattern).mapNotNull { getRaw(it)?.deserializeValue(clazz) } + } + + suspend fun delete(keys: Iterable) { + keys.forEach { delete(it) } + } + + suspend fun deleteAll() { + findAllKeys().forEach { delete(it) } + } + + // --------------------------------------------------------------------------------------------------------------------------------- + + fun T.serializeValue(): ByteArray = mapper.writeValueAsBytes(this) + fun ByteArray.deserializeValue(clazz: Class): T = mapper.readValue(this, clazz) + fun ByteArray.deserializeValue(typeRef: TypeReference): T = mapper.readValue(this, typeRef) + fun ByteArray.deserializeValue(javaType: JavaType): T = mapper.readValue(this, javaType) +} \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/CacheServiceExtensions.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/CacheServiceExtensions.kt new file mode 100644 index 0000000..3d868e8 --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/CacheServiceExtensions.kt @@ -0,0 +1,29 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache + +/** + * @author sokomishalov + */ +suspend inline fun CacheService.getOne(key: String, orElse: () -> T? = { null }): T? = getOne(key, T::class.java) ?: orElse() + +suspend inline fun CacheService.getList(key: String, ifEmpty: () -> List = { emptyList() }): List = getList(key, T::class.java).ifEmpty { ifEmpty() } + +suspend inline fun CacheService.getMap(key: String, ifEmpty: () -> Map = { emptyMap() }): Map = getMap(key, T::class.java).ifEmpty { ifEmpty() } + +suspend inline fun CacheService.getFromMap(key: String, mapKey: String, orElse: () -> T? = { null }): T? = getFromMap(key, mapKey, T::class.java) ?: orElse() + +suspend inline fun CacheService.find(glob: String, ifEmpty: () -> List = { emptyList() }): List = find(glob, T::class.java).ifEmpty { ifEmpty() } \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/inmemory/ConcurrentMapCacheService.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/inmemory/ConcurrentMapCacheService.kt new file mode 100644 index 0000000..95890bb --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/inmemory/ConcurrentMapCacheService.kt @@ -0,0 +1,58 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("unused") + +package ru.sokomishalov.commons.cache.inmemory + +import com.fasterxml.jackson.databind.ObjectMapper +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.core.common.unit +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER +import ru.sokomishalov.commons.core.string.convertGlobToRegex +import java.time.temporal.TemporalAmount +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +/** + * @author sokomishalov + */ +class ConcurrentMapCacheService( + override val cacheName: String = "cache:", + override val mapper: ObjectMapper = OBJECT_MAPPER, + private val map: ConcurrentMap = ConcurrentHashMap() +) : CacheService { + + companion object : Loggable + + private fun String.addPrefix(): String = "${cacheName}${this}" + private fun String.removePrefix(): String = removePrefix(cacheName) + + override suspend fun getRaw(key: String): ByteArray? = map[key.addPrefix()] + + override suspend fun putRaw(key: String, value: ByteArray) = map.put(key.addPrefix(), value).unit() + + override suspend fun delete(key: String) = map.remove(key.addPrefix()).unit() + + override suspend fun expire(key: String, ttl: TemporalAmount) = logWarn("expire() is unsupported") + + override suspend fun findKeys(glob: String): List = map.keys.map { it.removePrefix() }.filter { glob.convertGlobToRegex().matches(it) } + + + override suspend fun deleteAll() = map.clear() + + // override some methods for better performance +} \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/mongo/MongoCacheService.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/mongo/MongoCacheService.kt new file mode 100644 index 0000000..99986d1 --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/mongo/MongoCacheService.kt @@ -0,0 +1,109 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("DEPRECATION") + +package ru.sokomishalov.commons.cache.mongo + +import com.fasterxml.jackson.databind.ObjectMapper +import com.mongodb.ConnectionString +import com.mongodb.client.model.Filters.eq +import com.mongodb.client.model.FindOneAndUpdateOptions +import com.mongodb.client.model.Updates.set +import com.mongodb.reactivestreams.client.MongoClient +import com.mongodb.reactivestreams.client.MongoClients +import com.mongodb.reactivestreams.client.MongoCollection +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.bson.Document +import org.bson.conversions.Bson +import reactor.core.publisher.toFlux +import reactor.core.publisher.toMono +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.core.consts.LOCALHOST +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.core.reactor.await +import ru.sokomishalov.commons.core.reactor.awaitUnit +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER +import ru.sokomishalov.commons.core.string.convertGlobToRegex +import java.time.temporal.TemporalAmount +import java.util.Base64.getDecoder +import java.util.Base64.getEncoder + +/** + * @author sokomishalov + */ +class MongoCacheService( + override val cacheName: String = "cache", + override val mapper: ObjectMapper = OBJECT_MAPPER, + private val host: String = LOCALHOST, + private val port: Int = 27017, + private val databaseName: String = "lockDB", + private val client: MongoClient = MongoClients.create(ConnectionString("mongodb://${host}:${port}/${databaseName}")) +) : CacheService { + + companion object : Loggable { + private const val ID_FIELD = "_id" + private const val RAW_VALUE_FIELD = "rawValue" + } + + private val collection: MongoCollection get() = client.getDatabase(databaseName).getCollection(cacheName) + + override suspend fun getRaw(key: String): ByteArray? { + return collection + .find(key.buildKeyBson()) + .toMono() + .await() + ?.get(RAW_VALUE_FIELD) + ?.let { (it as String).decodeBase64() } + } + + override suspend fun putRaw(key: String, value: ByteArray) { + collection + .findOneAndUpdate(key.buildKeyBson(), value.buildValueBson(), FindOneAndUpdateOptions().upsert(true)) + .toMono() + .awaitUnit() + } + + override suspend fun expire(key: String, ttl: TemporalAmount) { + logWarn("expire() is unsupported") + } + + override suspend fun delete(key: String) { + collection + .deleteOne(key.buildKeyBson()) + .toMono() + .awaitUnit() + } + + override suspend fun findKeys(glob: String): List { + return collection + .find(eq(ID_FIELD, glob.convertGlobToRegex().toPattern())) + .toFlux() + .await() + .mapNotNull { it?.get(ID_FIELD) as String? } + } + + override suspend fun deleteAll() { + collection.drop().awaitFirstOrNull() + } + + private fun String.buildKeyBson(): Bson = eq(ID_FIELD, this) + private fun ByteArray.buildValueBson(): Bson = set(RAW_VALUE_FIELD, this.encodeBase64()) + + private fun ByteArray.encodeBase64() = getEncoder().encodeToString(this) + private fun String.decodeBase64() = getDecoder().decode(this) + + // override some methods for better performance +} \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/redis/RedisCacheService.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/redis/RedisCacheService.kt new file mode 100644 index 0000000..9f51a0e --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/redis/RedisCacheService.kt @@ -0,0 +1,62 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.redis + +import com.fasterxml.jackson.databind.ObjectMapper +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisURI +import io.lettuce.core.ScanArgs +import io.lettuce.core.api.StatefulRedisConnection +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.core.consts.LOCALHOST +import ru.sokomishalov.commons.core.reactor.await +import ru.sokomishalov.commons.core.reactor.awaitUnit +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER +import java.time.temporal.ChronoUnit.SECONDS +import java.time.temporal.TemporalAmount +import kotlin.Long.Companion.MAX_VALUE + + +/** + * @author sokomishalov + */ +open class RedisCacheService( + override val cacheName: String = "cache", + override val mapper: ObjectMapper = OBJECT_MAPPER, + private val host: String = LOCALHOST, + private val port: Int = 6379, + private val client: RedisClient = RedisClient.create(RedisURI.create(host, port)), + private val connection: StatefulRedisConnection = client.connect(StringByteArrayCodec()) +) : CacheService { + + private fun String.addPrefix(): String = "${cacheName}${this}" + private fun String.removePrefix(): String = removePrefix(cacheName) + + override suspend fun getRaw(key: String): ByteArray? = connection.reactive().get(key.addPrefix()).await() + + override suspend fun putRaw(key: String, value: ByteArray) = connection.reactive().set(key.addPrefix(), value).awaitUnit() + + override suspend fun delete(key: String) = connection.reactive().del(key.addPrefix()).awaitUnit() + + override suspend fun expire(key: String, ttl: TemporalAmount) = connection.reactive().expire(key.addPrefix(), ttl.get(SECONDS)).awaitUnit() + + override suspend fun findKeys(glob: String): List { + val scanArgs = ScanArgs().match(glob).limit(MAX_VALUE) + return connection.reactive().scan(scanArgs).await()?.keys?.map { it.removePrefix() } ?: emptyList() + } + + // override some methods for better performance +} \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/redis/StringByteArrayCodec.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/redis/StringByteArrayCodec.kt new file mode 100644 index 0000000..3ed710f --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/redis/StringByteArrayCodec.kt @@ -0,0 +1,20 @@ +package ru.sokomishalov.commons.cache.redis + +import io.lettuce.core.codec.ByteArrayCodec +import io.lettuce.core.codec.RedisCodec +import io.lettuce.core.codec.StringCodec +import java.nio.ByteBuffer +import kotlin.text.Charsets.UTF_8 + +/** + * @author sokomishalov + */ +internal class StringByteArrayCodec( + private val keyCodec: RedisCodec = StringCodec(UTF_8), + private val valueCodec: RedisCodec = ByteArrayCodec() +) : RedisCodec { + override fun encodeKey(key: String?): ByteBuffer = keyCodec.encodeKey(key) + override fun decodeKey(bytes: ByteBuffer?): String = keyCodec.decodeKey(bytes) + override fun encodeValue(value: ByteArray?): ByteBuffer = valueCodec.encodeValue(value) + override fun decodeValue(bytes: ByteBuffer?): ByteArray = valueCodec.decodeValue(bytes) +} \ No newline at end of file diff --git a/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/spring/SpringCacheService.kt b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/spring/SpringCacheService.kt new file mode 100644 index 0000000..0e95916 --- /dev/null +++ b/commons-cache/src/main/kotlin/ru/sokomishalov/commons/cache/spring/SpringCacheService.kt @@ -0,0 +1,54 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.spring + +import com.fasterxml.jackson.databind.ObjectMapper +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.core.common.unit +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER +import java.time.temporal.TemporalAmount +import org.springframework.cache.CacheManager as SpringCacheManager +import org.springframework.cache.concurrent.ConcurrentMapCacheManager as SpringConcurrentMapCacheManager + +/** + * @author sokomishalov + */ +class SpringCacheService( + override val cacheName: String = "cache", + override val mapper: ObjectMapper = OBJECT_MAPPER, + private val cacheManager: SpringCacheManager = SpringConcurrentMapCacheManager(cacheName) +) : CacheService { + + companion object : Loggable + + override suspend fun getRaw(key: String): ByteArray? = cacheManager.getCache(cacheName)?.get(key)?.get() as ByteArray? + + override suspend fun putRaw(key: String, value: ByteArray) = cacheManager.getCache(cacheName)?.put(key, value).unit() + + override suspend fun expire(key: String, ttl: TemporalAmount) = logWarn("expire() is unsupported") + + override suspend fun delete(key: String) = cacheManager.getCache(cacheName)?.evict(key).unit() + + override suspend fun findKeys(glob: String): List { + logWarn("findKeys() is unsupported") + return emptyList() + } + + override suspend fun deleteAll() = cacheManager.getCache(cacheName)?.clear().unit() + + // override some methods for better performance +} \ No newline at end of file diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/AbstractCacheServiceTest.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/AbstractCacheServiceTest.kt new file mode 100644 index 0000000..eff9efd --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/AbstractCacheServiceTest.kt @@ -0,0 +1,244 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache + +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import ru.sokomishalov.commons.cache.util.DummyModel +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.core.random.randomString +import java.util.UUID.randomUUID +import kotlin.Long.Companion.MAX_VALUE +import kotlin.Long.Companion.MIN_VALUE + +abstract class AbstractCacheServiceTest { + + companion object : Loggable { + private const val CACHE_KEY = "key" + private const val CACHE_VALUE = "value" + } + + protected abstract val cacheService: CacheService + + @After + open fun tearDown() { + runBlocking { + cacheService.deleteAll() + } + } + + @Test + open fun `Put strings`() { + runBlocking { + val data = listOf( + "key1" to "value1", + "key2" to "value2", + "key3" to "value3" + ) + data.forEach { (key, value) -> + cacheService.put(key, value) + } + + data.shuffled().forEach { (key, value) -> + assertEquals(value, cacheService.getOne(key)) + } + } + } + + @Test + open fun `Put and delete strings`() { + runBlocking { + cacheService.put(CACHE_KEY, CACHE_VALUE) + assertEquals(CACHE_VALUE, cacheService.getOne(CACHE_KEY)) + + cacheService.delete(CACHE_KEY) + assertNull(cacheService.getOne(CACHE_KEY)) + } + } + + @Test + open fun `Put and replace strings`() { + runBlocking { + cacheService.put(CACHE_KEY, CACHE_VALUE) + assertEquals(CACHE_VALUE, cacheService.getOne(CACHE_KEY)) + + val newValue = "newValue" + cacheService.put(CACHE_KEY, newValue) + assertEquals(newValue, cacheService.getOne(CACHE_KEY)) + } + } + + @Test + open fun `Put objects`() { + runBlocking { + val data = mutableListOf( + "key1" to DummyModel(MIN_VALUE, "firstDummy"), + "key2" to DummyModel(0, "secondDummy"), + "key3" to DummyModel(MAX_VALUE, "thirdDummy") + ) + data.forEach { (key, value) -> + cacheService.put(key, value) + } + + data.shuffled().forEach { (key, value) -> + assertEquals(value, cacheService.getOne(key)) + } + } + } + + @Test + open fun `Put and delete objects`() { + runBlocking { + val dummy = DummyModel(1, "DummyModel") + cacheService.put(CACHE_KEY, dummy) + assertEquals(dummy, cacheService.getOne(CACHE_KEY)) + + cacheService.delete(CACHE_KEY) + assertNull(cacheService.getOne(CACHE_KEY)) + } + } + + @Test + open fun `Put and replace object`() { + runBlocking { + val firstDummy = DummyModel(MIN_VALUE, "firstDummy") + cacheService.put(CACHE_KEY, firstDummy) + assertEquals(firstDummy, cacheService.getOne(CACHE_KEY)) + + val secondDummy = DummyModel(MAX_VALUE, "secondDummy") + cacheService.put(CACHE_KEY, secondDummy) + assertEquals(secondDummy, cacheService.getOne(CACHE_KEY)) + } + } + + @Test + open fun `Get not existing value`() { + runBlocking { + assertNull(cacheService.getOne(randomString(10))) + assertEquals("kek", cacheService.getOne(randomString(10)) { "kek" }) + } + } + + @Test + open fun `Put string list`() { + runBlocking { + val data = listOf("value1", "value2", "value3") + cacheService.put(CACHE_KEY, data) + + val result = cacheService.getList(CACHE_KEY) + assertEquals(data, result.sorted()) + } + } + + @Test + open fun `Put object list`() { + runBlocking { + val data = mutableListOf( + DummyModel(1, "aFirstDummy"), + DummyModel(2, "bSecondDummy"), + DummyModel(3, "cThirdDummy") + ) + cacheService.put(CACHE_KEY, data) + + val result = cacheService.getList(CACHE_KEY) + assertEquals(data, result.sorted()) + } + } + + @Test + open fun `Put empty list`() { + runBlocking { + cacheService.put(CACHE_KEY, emptyList()) + assertEquals(emptyList(), cacheService.getList(CACHE_KEY)) + } + } + + @Test + open fun `Get non existing list`() { + runBlocking { + assertEquals(emptyList(), cacheService.getList(CACHE_KEY)) + assertEquals(listOf("kek"), cacheService.getList(CACHE_KEY) { listOf("kek") }) + } + } + + @Test + open fun `Put map`() { + runBlocking { + val data = mapOf( + "key1" to "value1", + "key2" to "value2", + "key3" to "value3" + ) + cacheService.put(CACHE_KEY, data) + + assertEquals(data, cacheService.getMap(CACHE_KEY)) + data.forEach { (key, value) -> + assertEquals(value, cacheService.getFromMap(CACHE_KEY, key)) + } + } + } + + @Test + open fun `Put empty map`() { + runBlocking { + cacheService.put(CACHE_KEY, emptyMap()) + assertEquals(emptyMap(), cacheService.getMap(CACHE_KEY)) + } + } + + @Test + open fun `Get non existing map`() { + runBlocking { + assertEquals(emptyMap(), cacheService.getMap(randomUUID().toString())) + } + } + + @Test + open fun `Get from not existing map`() { + runBlocking { + assertNull(cacheService.getFromMap(randomUUID().toString(), randomUUID().toString())) + assertEquals("kek", cacheService.getFromMap(randomUUID().toString(), randomUUID().toString()) { "kek" }) + } + } + + @Test + open fun `Test find by glob pattern`() { + runBlocking { + val data = mapOf( + "kekpek1" to "kekpek1", + "kekpek2" to "kekpek2", + "cheburek1" to "cheburek1", + "cheburek2" to "cheburek2" + ) + data.forEach { (k, v) -> cacheService.put(k, v) } + + val all = cacheService.find("*") + assertEquals(4, all.size) + + val found = cacheService.find("*kek*") + assertEquals(2, found.size) + + val notFound = cacheService.find("*hehmda") + assertEquals(0, notFound.size) + + val notFoundButElse = cacheService.find("*hehmda") { listOf("lol") } + assertEquals(1, notFoundButElse.size) + } + } +} diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/inmemory/ConcurrentMapCacheServiceTest.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/inmemory/ConcurrentMapCacheServiceTest.kt new file mode 100644 index 0000000..75f8644 --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/inmemory/ConcurrentMapCacheServiceTest.kt @@ -0,0 +1,29 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.inmemory + +import ru.sokomishalov.commons.cache.AbstractCacheServiceTest +import ru.sokomishalov.commons.cache.CacheService + + +/** + * @author sokomishalov + */ +class ConcurrentMapCacheServiceTest : AbstractCacheServiceTest() { + + override val cacheService: CacheService by lazy { ConcurrentMapCacheService() } + +} \ No newline at end of file diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/mongo/MongoCacheServiceTest.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/mongo/MongoCacheServiceTest.kt new file mode 100644 index 0000000..3a085ee --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/mongo/MongoCacheServiceTest.kt @@ -0,0 +1,46 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.mongo + +import org.junit.AfterClass +import org.junit.ClassRule +import ru.sokomishalov.commons.cache.AbstractCacheServiceTest +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.cache.util.MongoTestContainer +import ru.sokomishalov.commons.cache.util.createDefaultMongoContainer +import ru.sokomishalov.commons.cache.util.createReactiveMongoClient +import ru.sokomishalov.commons.core.log.Loggable + +/** + * @author sokomishalov + */ +class MongoCacheServiceTest : AbstractCacheServiceTest() { + + companion object : Loggable { + @get:ClassRule + val mongo: MongoTestContainer = createDefaultMongoContainer() + + @AfterClass + @JvmStatic + fun stop() = mongo.stop() + } + + override val cacheService: CacheService by lazy { + mongo.start() + logInfo(mongo.logs) + MongoCacheService(client = mongo.createReactiveMongoClient()) + } +} \ No newline at end of file diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/redis/RedisCacheServiceTest.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/redis/RedisCacheServiceTest.kt new file mode 100644 index 0000000..96f9c4c --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/redis/RedisCacheServiceTest.kt @@ -0,0 +1,46 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.redis + +import org.junit.AfterClass +import org.junit.ClassRule +import ru.sokomishalov.commons.cache.AbstractCacheServiceTest +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.cache.util.RedisTestContainer +import ru.sokomishalov.commons.cache.util.createDefaultRedisContainer +import ru.sokomishalov.commons.cache.util.createRedisClient +import ru.sokomishalov.commons.core.log.Loggable + +/** + * @author sokomishalov + */ +class RedisCacheServiceTest : AbstractCacheServiceTest() { + + companion object : Loggable { + @get:ClassRule + val redis: RedisTestContainer = createDefaultRedisContainer() + + @AfterClass + @JvmStatic + fun stop() = redis.stop() + } + + override val cacheService: CacheService by lazy { + redis.start() + logInfo(redis.logs) + RedisCacheService(client = redis.createRedisClient()) + } +} \ No newline at end of file diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/spring/SpringCacheServiceTest.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/spring/SpringCacheServiceTest.kt new file mode 100644 index 0000000..a82e4e7 --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/spring/SpringCacheServiceTest.kt @@ -0,0 +1,31 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.spring + +import org.junit.Assert.assertTrue +import ru.sokomishalov.commons.cache.AbstractCacheServiceTest +import ru.sokomishalov.commons.cache.CacheService + +/** + * @author sokomishalov + */ +class SpringCacheServiceTest : AbstractCacheServiceTest() { + + override val cacheService: CacheService by lazy { SpringCacheService(cacheName = "cache") } + + // not realized yet + override fun `Test find by glob pattern`() = assertTrue(true) +} \ No newline at end of file diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/DummyModel.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/DummyModel.kt new file mode 100644 index 0000000..7b8388f --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/DummyModel.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.util + +import ru.sokomishalov.commons.core.random.randomString +import java.lang.System.currentTimeMillis + +data class DummyModel( + val id: Long = 0, + val name: String? = randomString(10), + val createdAt: Long = currentTimeMillis() +) : Comparable { + + override fun compareTo(other: DummyModel): Int = id.compareTo(other.id) +} diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/MongoTestContainer.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/MongoTestContainer.kt new file mode 100644 index 0000000..35f7810 --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/MongoTestContainer.kt @@ -0,0 +1,38 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.util + +import com.mongodb.ConnectionString +import com.mongodb.reactivestreams.client.MongoClient +import com.mongodb.reactivestreams.client.MongoClients +import org.testcontainers.containers.GenericContainer + +/** + * @author sokomishalov + */ + +class MongoTestContainer : GenericContainer("mvertes/alpine-mongo") + +internal fun createDefaultMongoContainer(): MongoTestContainer { + return MongoTestContainer().apply { + withReuse(true) + withExposedPorts(27017) + } +} + +fun MongoTestContainer.createReactiveMongoClient(): MongoClient { + return MongoClients.create(ConnectionString("mongodb://${containerIpAddress}:${firstMappedPort}")) +} \ No newline at end of file diff --git a/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/RedisTestContainer.kt b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/RedisTestContainer.kt new file mode 100644 index 0000000..06ecde3 --- /dev/null +++ b/commons-cache/src/test/kotlin/ru/sokomishalov/commons/cache/util/RedisTestContainer.kt @@ -0,0 +1,38 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.cache.util + +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisURI +import org.testcontainers.containers.GenericContainer +import java.time.Duration + +/** + * @author sokomishalov + */ + +class RedisTestContainer : GenericContainer("redis:alpine") + +internal fun createDefaultRedisContainer(): RedisTestContainer { + return RedisTestContainer().apply { + withReuse(true) + withExposedPorts(6379) + } +} + +fun RedisTestContainer.createRedisClient(timeout: Duration = Duration.ofSeconds(1)): RedisClient { + return RedisClient.create(RedisURI(containerIpAddress, firstMappedPort, timeout)) +} \ No newline at end of file diff --git a/commons-cache/src/test/resources/tarantool/app.lua b/commons-cache/src/test/resources/tarantool/app.lua new file mode 100644 index 0000000..572e9a1 --- /dev/null +++ b/commons-cache/src/test/resources/tarantool/app.lua @@ -0,0 +1,30 @@ +--[[ + + Copyright 2019-2019 the original author or authors. + + 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. + +]] +box.cfg {} + +box.once("schema", function() + box.schema.space.create('cache') + box.space.cache:format({ + { name = 'key', type = 'string' }, + { name = 'value', type = 'string' } + }) + box.space.cache:create_index('primary', { + type = 'hash', + parts = { 'key' } + }) +end) \ No newline at end of file diff --git a/commons-core/pom.xml b/commons-core/pom.xml index bebfd83..4023e32 100644 --- a/commons-core/pom.xml +++ b/commons-core/pom.xml @@ -7,29 +7,11 @@ ru.sokomishalov.commons commons-parent - 1.0.29 + 1.1.0 commons-core - 1.0.29 - - - 1.3.61 - 1.3.2 - 2.10.1 - 3.9 - 2.6 - 4.4 - 28.1-jre - 3.3.1.RELEASE - 0.9.2.RELEASE - 3.3.1.RELEASE - 1.7.29 - 1.2.3 - 4.1.0 - 1.12.1 - - + 1.1.0 @@ -40,91 +22,6 @@ kotlin-stdlib-jdk8 ${kotlin.version} - - org.jetbrains.kotlin - kotlin-reflect - ${kotlin.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-core - ${kotlinx.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-reactor - ${kotlinx.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-reactive - ${kotlinx.version} - - - org.jetbrains.kotlinx - kotlinx-coroutines-jdk8 - ${kotlinx.version} - - - - io.projectreactor - reactor-core - ${reactor.version} - - - io.projectreactor.netty - reactor-netty - ${reactor-netty.version} - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.module - jackson-module-kotlin - ${jackson.version} - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - ${jackson.version} - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - ${jackson.version} - - - - org.apache.commons - commons-lang3 - ${commons-lang.version} - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - - - org.jeasy - easy-random-core - ${easy-random.version} - true - - - - org.jsoup - jsoup - ${jsoup.version} - true - diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/html/ScrapeUtils.kt b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/html/ScrapeUtils.kt deleted file mode 100644 index 00c0db2..0000000 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/html/ScrapeUtils.kt +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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. - */ -@file:Suppress("unused") - -package ru.sokomishalov.commons.core.html - -import io.netty.handler.codec.http.HttpHeaderNames.USER_AGENT -import org.jsoup.Jsoup.clean -import org.jsoup.Jsoup.parse -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import org.jsoup.safety.Whitelist -import ru.sokomishalov.commons.core.http.REACTIVE_NETTY_HTTP_CLIENT -import ru.sokomishalov.commons.core.reactor.awaitStrict -import ru.sokomishalov.commons.core.string.isNotNullOrBlank -import java.nio.charset.StandardCharsets.UTF_8 - - -/** - * @author sokomishalov - */ -suspend fun getWebPage( - url: String, - userAgent: String? = null -): Document { - return REACTIVE_NETTY_HTTP_CLIENT - .headers { - if (userAgent.isNotNullOrBlank()) { - it.set(USER_AGENT, userAgent) - } - } - .get() - .uri(url) - .responseContent() - .aggregate() - .asString(UTF_8) - .awaitStrict() - .let { parse(it) } -} - -fun Element.getSingleElementByClass(name: String): Element { - return getElementsByClass(name).first() -} - -fun Element.getSingleElementByTag(name: String): Element { - return getElementsByTag(name).first() -} - -fun Element.getImageBackgroundUrl(): String { - val style = attr("style") - return style.substring(style.indexOf("http"), style.indexOf(")")) -} - -fun Element.fixText(): String { - return clean(toString(), Whitelist.simpleText()) -} \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/images/ImageUtils.kt b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/images/ImageUtils.kt deleted file mode 100644 index fe06f86..0000000 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/images/ImageUtils.kt +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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. - */ -@file:Suppress("unused", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") - -package ru.sokomishalov.commons.core.images - -import ru.sokomishalov.commons.core.http.REACTIVE_NETTY_HTTP_CLIENT -import ru.sokomishalov.commons.core.reactor.awaitStrict -import java.awt.image.BufferedImage -import java.io.ByteArrayInputStream -import javax.imageio.ImageIO - - -fun ByteArray.toBufferedImage(): BufferedImage { - return ByteArrayInputStream(this).use { - ImageIO.read(it) - } -} - -suspend fun getImageByteArray(url: String?, orElse: ByteArray = ByteArray(0)): ByteArray { - return runCatching { - REACTIVE_NETTY_HTTP_CLIENT - .get() - .uri(url) - .responseContent() - .aggregate() - .asByteArray() - .awaitStrict() - }.getOrElse { - orElse - } -} - -suspend fun getImageDimensions(url: String?, default: Pair = 1 to 1): Pair { - return runCatching { - val imageByteArray = getImageByteArray(url) - imageByteArray.toBufferedImage().run { width to height } - }.getOrElse { - default - } -} - -suspend fun getImageAspectRatio(url: String?): Double { - return getImageDimensions(url).run { first.toDouble().div(second) } -} - -suspend fun checkImageUrl(url: String?): Boolean { - return runCatching { - getImageByteArray(url).toBufferedImage() - true - }.getOrElse { - false - } -} \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/JEasyRandomUtils.kt b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/JEasyRandomUtils.kt deleted file mode 100644 index d9c8434..0000000 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/JEasyRandomUtils.kt +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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. - */ -@file:Suppress("unused") - -package ru.sokomishalov.commons.core.random - -import org.jeasy.random.EasyRandom -import org.jeasy.random.EasyRandomParameters -import java.nio.charset.StandardCharsets.UTF_8 - -/** - * @author sokomishalov - */ -val EASY_RANDOM_PARAMS: EasyRandomParameters = EasyRandomParameters() - .seed((0L..1000L).random()) - .objectPoolSize(100) - .randomizationDepth(3) - .charset(UTF_8) - .stringLengthRange(5, 20) - .collectionSizeRange(0, 10) - .scanClasspathForConcreteTypes(true) - .overrideDefaultInitialization(false) - .ignoreRandomizationErrors(true) - - -inline fun randomPojo(params: EasyRandomParameters = EASY_RANDOM_PARAMS): T { - return EasyRandom(params).nextObject(T::class.java) -} - -inline fun randomPojoSequence(params: EasyRandomParameters = EASY_RANDOM_PARAMS) = sequence { - while (true) { - yield(randomPojo(params)) - } -} \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/RandomStringUtils.kt b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/RandomStringUtils.kt index 137dfad..7a0a501 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/RandomStringUtils.kt +++ b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/random/RandomStringUtils.kt @@ -17,14 +17,29 @@ package ru.sokomishalov.commons.core.random -import org.apache.commons.lang3.RandomStringUtils.random - /** * @author sokomishalov */ +private val LOWER_CASE_ALPHABET: CharRange = ('a'..'z') +private val UPPER_CASE_ALPHABET: CharRange = ('A'..'Z') +private val NUMBERS: CharRange = ('0'..'9') + fun randomString( length: Int = 20, useLetters: Boolean = true, - useDigits: Boolean = false -): String = random(length, useLetters, useDigits) \ No newline at end of file + useDigits: Boolean = true, + lowerCase: Boolean = true, + upperCase: Boolean = true +): String { + require(useLetters || useDigits) + require(!useLetters || lowerCase || upperCase) + + val fullAlphabet: MutableList = mutableListOf() + + if (useDigits) fullAlphabet += NUMBERS + if (useLetters && lowerCase) fullAlphabet += LOWER_CASE_ALPHABET + if (useLetters && upperCase) fullAlphabet += UPPER_CASE_ALPHABET + + return List(length) { fullAlphabet.random() }.joinToString("") +} \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/reflection/ReflectionUtils.kt b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/reflection/ReflectionUtils.kt index 726f638..cc466d5 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/reflection/ReflectionUtils.kt +++ b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/reflection/ReflectionUtils.kt @@ -17,13 +17,14 @@ package ru.sokomishalov.commons.core.reflection -import kotlin.reflect.full.companionObject - /** * @author sokomishalov */ -fun unwrapCompanionClass(ofClass: Class): Class<*> { - return ofClass.enclosingClass?.takeIf { - ofClass.enclosingClass.kotlin.companionObject?.java == ofClass - } ?: ofClass + + +fun Class.unwrapCompanionClass(): Class<*> { + return when { + name.endsWith("\$Companion") -> enclosingClass ?: this + else -> this + } } \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/string/StringUtils.kt b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/string/StringExtensions.kt similarity index 99% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/string/StringUtils.kt rename to commons-core/src/main/kotlin/ru/sokomishalov/commons/core/string/StringExtensions.kt index 3e53064..7c2068b 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/string/StringUtils.kt +++ b/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/string/StringExtensions.kt @@ -32,7 +32,6 @@ inline fun CharSequence?.isNotNullOrBlank(): Boolean { return (this == null || this.isBlank()).not() } - // https://stackoverflow.com/a/1248627/5843129 fun String.convertGlobToRegex(): Regex { val sb = StringBuilder(length) diff --git a/commons-core/src/test/kotlin/ru/sokomishalov/commons/core/images/ImageUtilsTest.kt b/commons-core/src/test/kotlin/ru/sokomishalov/commons/core/images/ImageUtilsTest.kt deleted file mode 100644 index e3b536b..0000000 --- a/commons-core/src/test/kotlin/ru/sokomishalov/commons/core/images/ImageUtilsTest.kt +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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 ru.sokomishalov.commons.core.images - -import kotlinx.coroutines.runBlocking -import org.junit.Assert.* -import org.junit.Test -import kotlin.math.abs - -class ImageUtilsTest { - - private val imageWidth = 200 - private val imageHeight = 300 - private val imageUrl = "https://picsum.photos/$imageWidth/$imageHeight" - private val invalidImageUrl = "https://lol.kek/cheburek" - private val imageUrl401 = "https://httpstat.us/401" - private val imageUrl404 = "https://httpstat.us/404" - - - @Test - fun `Get random image by url and check dimensions`() { - val imageDimensions = runBlocking { getImageDimensions(imageUrl) } - - assertEquals(imageWidth, imageDimensions.first) - assertEquals(imageHeight, imageDimensions.second) - } - - @Test - fun `Get random image by url and check aspect ratio`() { - val expected = imageWidth.toDouble().div(imageHeight) - val result = runBlocking { getImageAspectRatio(imageUrl) } - - assertTrue(abs(expected - result) < 0.01) - } - - @Test - fun `Check image availability`() { - runBlocking { - assertTrue(checkImageUrl(imageUrl)) - assertFalse(checkImageUrl(invalidImageUrl)) - assertFalse(checkImageUrl(imageUrl401)) - assertFalse(checkImageUrl(imageUrl404)) - } - } -} \ No newline at end of file diff --git a/commons-coroutines/pom.xml b/commons-coroutines/pom.xml new file mode 100644 index 0000000..4c9e069 --- /dev/null +++ b/commons-coroutines/pom.xml @@ -0,0 +1,117 @@ + + + 4.0.0 + + + ru.sokomishalov.commons + commons-parent + 1.1.0 + + + commons-coroutines + 1.1.0 + + + + ru.sokomishalov.commons + commons-core + 1.1.0 + + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + ${kotlinx.version} + + + org.jetbrains.kotlinx + kotlinx-coroutines-jdk8 + ${kotlinx.version} + + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactive + ${kotlinx.version} + + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + ${kotlinx.version} + + + + + + ${project.basedir}/src/main/kotlin + ${project.artifactId} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + -Xuse-experimental=kotlin.Experimental + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + kapt + + kapt + + + + ${project.basedir}/src/main/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineIterableExtensions.kt b/commons-coroutines/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineIterableExtensions.kt similarity index 100% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineIterableExtensions.kt rename to commons-coroutines/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineIterableExtensions.kt diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineMapExtensions.kt b/commons-coroutines/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineMapExtensions.kt similarity index 100% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineMapExtensions.kt rename to commons-coroutines/src/main/kotlin/ru/sokomishalov/commons/core/collections/CoroutineMapExtensions.kt diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/reactor/ReactorUtils.kt b/commons-coroutines/src/main/kotlin/ru/sokomishalov/commons/core/reactor/ReactorUtils.kt similarity index 100% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/reactor/ReactorUtils.kt rename to commons-coroutines/src/main/kotlin/ru/sokomishalov/commons/core/reactor/ReactorUtils.kt diff --git a/commons-distributed-locks/pom.xml b/commons-distributed-locks/pom.xml new file mode 100644 index 0000000..ab12b26 --- /dev/null +++ b/commons-distributed-locks/pom.xml @@ -0,0 +1,175 @@ + + + 4.0.0 + + + ru.sokomishalov.commons + commons-parent + 1.1.0 + + + commons-distributed-locks + 1.1.0 + + + + + + ru.sokomishalov.commons + commons-core + 1.1.0 + + + + ru.sokomishalov.commons + commons-coroutines + 1.1.0 + + + + ru.sokomishalov.commons + commons-serialization + 1.1.0 + + + + + + org.aspectj + aspectjweaver + ${aspectj.version} + true + + + + org.mongodb + mongodb-driver-reactivestreams + ${mongo-reactive.version} + true + + + + io.lettuce + lettuce-core + ${lettuce.version} + true + + + + + + ru.sokomishalov.commons + commons-logging + 1.1.0 + + + + org.testcontainers + testcontainers + ${test-containers.version} + test + + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + + org.springframework.boot + spring-boot-starter-aop + ${spring.boot.version} + test + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + ${project.artifactId} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + -Xuse-experimental=kotlin.Experimental + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + kapt + + kapt + + + + ${project.basedir}/src/main/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + + + + \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockExtensions.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockExtensions.kt similarity index 86% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockExtensions.kt rename to commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockExtensions.kt index 6e836ba..ed5aa9b 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockExtensions.kt +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockExtensions.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.sokomishalov.commons.spring.locks.cluster +package ru.sokomishalov.commons.distributed.locks import ru.sokomishalov.commons.core.string.isNotNullOrBlank import ru.sokomishalov.commons.core.url.HOSTNAME @@ -29,10 +29,10 @@ import java.time.ZonedDateTime.now * @param lockName name of lock * @param lockAtLeastFor will hold the lock for this duration, at a minimum * @param lockAtMostFor will hold the lock for this duration, at a maximum - * @param action to execute the given [action] under cluster lock. + * @param action to execute the given [action] under distributed lock. * @return the return value of the action or null if locked */ -suspend inline fun LockProvider.withClusterLock( +suspend inline fun DistributedLockProvider.withDistributedLock( lockName: String, lockAtLeastFor: Duration = ZERO, lockAtMostFor: Duration = lockAtLeastFor, @@ -45,10 +45,10 @@ suspend inline fun LockProvider.withClusterLock( val now = now() - val lockedAtLeastUntil = now.plus(lockAtLeastFor) - val lockedAtMostUntil = now.plus(lockAtMostFor) + val lockedAtLeastUntil = now + lockAtLeastFor + val lockedAtMostUntil = now + lockAtMostFor - val lockInfo = LockInfo( + val lockInfo = DistributedLockInfo( lockName = lockName, lockedBy = HOSTNAME, lockedAt = now, diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockInfo.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockInfo.kt similarity index 90% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockInfo.kt rename to commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockInfo.kt index cff1992..30bb104 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockInfo.kt +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockInfo.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.sokomishalov.commons.spring.locks.cluster +package ru.sokomishalov.commons.distributed.locks import java.time.ZonedDateTime @@ -21,7 +21,7 @@ import java.time.ZonedDateTime * @author sokomishalov */ -data class LockInfo( +data class DistributedLockInfo( val lockName: String, val lockedBy: String, val lockedAt: ZonedDateTime, diff --git a/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockProvider.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockProvider.kt new file mode 100644 index 0000000..727a51b --- /dev/null +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/DistributedLockProvider.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("unused") + +package ru.sokomishalov.commons.distributed.locks + +/** + * @author sokomishalov + */ +interface DistributedLockProvider { + + suspend fun tryLock(distributedLockInfo: DistributedLockInfo): Boolean + + suspend fun release(distributedLockInfo: DistributedLockInfo) +} \ No newline at end of file diff --git a/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/WithDistributedLock.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/WithDistributedLock.kt new file mode 100644 index 0000000..bf5d069 --- /dev/null +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/WithDistributedLock.kt @@ -0,0 +1,37 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks + +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlin.annotation.AnnotationTarget.* + +/** + * @author sokomishalov + */ + +@Target(FUNCTION, PROPERTY_SETTER, PROPERTY_GETTER) +@Retention(RUNTIME) +annotation class WithDistributedLock( + + // name of lock + val lockName: String = "lock", + + // will hold the lock for this duration, at a minimum + val lockAtLeastForMs: Long = 0, + + // will hold the lock for this duration, at a maximum + val lockAtMostForMs: Long = 0 +) \ No newline at end of file diff --git a/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/WithDistributedLockAspect.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/WithDistributedLockAspect.kt new file mode 100644 index 0000000..6209c98 --- /dev/null +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/WithDistributedLockAspect.kt @@ -0,0 +1,44 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks + +import kotlinx.coroutines.runBlocking +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.aspectj.lang.reflect.MethodSignature +import java.time.Duration.ofMillis + +@Aspect +class WithDistributedLockAspect(private val distributedLockProvider: DistributedLockProvider) { + + @Around("@annotation(ru.sokomishalov.commons.distributed.locks.WithDistributedLock) && execution(* *.*(..))") + fun doActionWithDistributedLock(pjp: ProceedingJoinPoint): Any? { + val signature = pjp.signature as MethodSignature + val annotation = signature.method.getAnnotation(WithDistributedLock::class.java) + + return runBlocking { + distributedLockProvider.withDistributedLock( + lockName = annotation.lockName, + lockAtLeastFor = ofMillis(annotation.lockAtLeastForMs), + lockAtMostFor = ofMillis(annotation.lockAtMostForMs), + ifLockedValue = null + ) { + pjp.proceed() + } + } + } +} diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/mongo/MongoReactiveLockProvider.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoReactiveDistributedLockProvider.kt similarity index 72% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/mongo/MongoReactiveLockProvider.kt rename to commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoReactiveDistributedLockProvider.kt index 87f0236..eb330b1 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/mongo/MongoReactiveLockProvider.kt +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoReactiveDistributedLockProvider.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.sokomishalov.commons.spring.locks.cluster.mongo +package ru.sokomishalov.commons.distributed.locks.mongo import com.mongodb.ErrorCategory.DUPLICATE_KEY import com.mongodb.ErrorCategory.fromErrorCode @@ -25,29 +25,27 @@ import com.mongodb.client.model.Updates.set import com.mongodb.reactivestreams.client.MongoClient import com.mongodb.reactivestreams.client.MongoClients import com.mongodb.reactivestreams.client.MongoCollection -import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.reactive.awaitSingle import org.bson.Document -import org.springframework.boot.autoconfigure.mongo.MongoProperties import ru.sokomishalov.commons.core.common.unit -import ru.sokomishalov.commons.spring.locks.cluster.LockInfo -import ru.sokomishalov.commons.spring.locks.cluster.LockProvider +import ru.sokomishalov.commons.distributed.locks.DistributedLockInfo +import ru.sokomishalov.commons.distributed.locks.DistributedLockProvider import java.util.* /** * @author sokomishalov */ -class MongoReactiveLockProvider( - private val mongoProperties: MongoProperties? = null, +class MongoReactiveDistributedLockProvider( private val client: MongoClient = MongoClients.create(), - private val databaseName: String = mongoProperties?.mongoClientDatabase ?: DEFAULT_DB_NAME, + private val databaseName: String = DEFAULT_DB_NAME, private val collectionName: String = DEFAULT_COLLECTION_NAME -) : LockProvider { +) : DistributedLockProvider { companion object { - private const val DEFAULT_DB_NAME = "clusterLockDb" - private const val DEFAULT_COLLECTION_NAME = "clusterLock" + private const val DEFAULT_DB_NAME = "distributedLockDb" + private const val DEFAULT_COLLECTION_NAME = "distributedLock" private const val ID_FIELD = "_id" private const val LOCKED_UNTIL_FIELD = "lockedUntil" private const val LOCKED_AT_FIELD = "lockedAt" @@ -60,19 +58,19 @@ class MongoReactiveLockProvider( * 2. The lock document exists and lockUntil before now - it is updated - we have the lock * 3. The lock document exists and lockUntil after now - Duplicate key exception is thrown */ - override suspend fun tryLock(lockInfo: LockInfo): Boolean { + override suspend fun tryLock(distributedLockInfo: DistributedLockInfo): Boolean { return try { getCollection() .findOneAndUpdate( - and(eq(ID_FIELD, lockInfo.lockName), lte(LOCKED_UNTIL_FIELD, Date())), + and(eq(ID_FIELD, distributedLockInfo.lockName), lte(LOCKED_UNTIL_FIELD, Date())), combine( - set(LOCKED_UNTIL_FIELD, Date.from(lockInfo.lockedUntil.toInstant())), - set(LOCKED_AT_FIELD, Date.from(lockInfo.lockedAt.toInstant())), - set(LOCKED_BY_FIELD, lockInfo.lockedBy) + set(LOCKED_UNTIL_FIELD, Date.from(distributedLockInfo.lockedUntil.toInstant())), + set(LOCKED_AT_FIELD, Date.from(distributedLockInfo.lockedAt.toInstant())), + set(LOCKED_BY_FIELD, distributedLockInfo.lockedBy) ), FindOneAndUpdateOptions().upsert(true) ) - .awaitFirstOrElse { null } + .awaitFirstOrNull() .unit() true } catch (e: MongoServerException) { @@ -83,11 +81,11 @@ class MongoReactiveLockProvider( } } - override suspend fun release(lockInfo: LockInfo) { + override suspend fun release(distributedLockInfo: DistributedLockInfo) { return getCollection() .findOneAndUpdate( - eq(ID_FIELD, lockInfo.lockName), - combine(set(LOCKED_UNTIL_FIELD, Date.from(lockInfo.lockedUntil.toInstant()))) + eq(ID_FIELD, distributedLockInfo.lockName), + combine(set(LOCKED_UNTIL_FIELD, Date.from(distributedLockInfo.lockedUntil.toInstant()))) ) .awaitSingle() .unit() diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/redis/RedisLettuceLockProvider.kt b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisLettuceDistributedLockProvider.kt similarity index 71% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/redis/RedisLettuceLockProvider.kt rename to commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisLettuceDistributedLockProvider.kt index 704f453..90eea8f 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/redis/RedisLettuceLockProvider.kt +++ b/commons-distributed-locks/src/main/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisLettuceDistributedLockProvider.kt @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.sokomishalov.commons.spring.locks.cluster.redis +package ru.sokomishalov.commons.distributed.locks.redis +import com.fasterxml.jackson.module.kotlin.readValue import io.lettuce.core.RedisClient import io.lettuce.core.RedisURI import io.lettuce.core.SetArgs @@ -23,23 +24,22 @@ import ru.sokomishalov.commons.core.consts.LOCALHOST import ru.sokomishalov.commons.core.reactor.await import ru.sokomishalov.commons.core.reactor.awaitUnit import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER -import ru.sokomishalov.commons.core.serialization.aReadValue import ru.sokomishalov.commons.core.string.isNotNullOrBlank -import ru.sokomishalov.commons.spring.locks.cluster.LockInfo -import ru.sokomishalov.commons.spring.locks.cluster.LockProvider +import ru.sokomishalov.commons.distributed.locks.DistributedLockInfo +import ru.sokomishalov.commons.distributed.locks.DistributedLockProvider import java.time.Duration import java.time.ZonedDateTime.now -class RedisLettuceLockProvider( +class RedisLettuceDistributedLockProvider( private val client: RedisClient = RedisClient.create(RedisURI.create(LOCALHOST)), private val connection: StatefulRedisConnection = client.connect(), private val keyPrefix: String = "", private val keyDelimiter: String = ":" -) : LockProvider { +) : DistributedLockProvider { - override suspend fun tryLock(lockInfo: LockInfo): Boolean { - val keyValue = lockInfo.buildKeyValue() - val expireAfterMs = Duration.between(now(), lockInfo.lockedUntil).toMillis() + override suspend fun tryLock(distributedLockInfo: DistributedLockInfo): Boolean { + val keyValue = distributedLockInfo.buildKeyValue() + val expireAfterMs = Duration.between(now(), distributedLockInfo.lockedUntil).toMillis() val lockAcquired = connection .reactive() @@ -55,15 +55,16 @@ class RedisLettuceLockProvider( } } - override suspend fun release(lockInfo: LockInfo) { - val keyValue = lockInfo.buildKeyValue() + override suspend fun release(distributedLockInfo: DistributedLockInfo) { + val keyValue = distributedLockInfo.buildKeyValue() connection .reactive() .set(keyValue.first, keyValue.second, SetArgs().xx()) .awaitUnit() } - private fun LockInfo.buildKeyValue(): Pair { + + private fun DistributedLockInfo.buildKeyValue(): Pair { val key = when { keyPrefix.isBlank() -> lockName else -> "$keyPrefix$keyDelimiter$lockName" @@ -73,7 +74,7 @@ class RedisLettuceLockProvider( return key to value } - private suspend fun String.deserializeValue(): LockInfo { - return OBJECT_MAPPER.aReadValue(this) + private fun String.deserializeValue(): DistributedLockInfo { + return OBJECT_MAPPER.readValue(this) } } \ No newline at end of file diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/AbstractDistributedLockAspectTest.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/AbstractDistributedLockAspectTest.kt new file mode 100644 index 0000000..8c8fd83 --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/AbstractDistributedLockAspectTest.kt @@ -0,0 +1,88 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.TestConfiguration +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.EnableAspectJAutoProxy +import org.springframework.stereotype.Component +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner +import java.util.concurrent.atomic.AtomicInteger + + +/** + * @author sokomishalov + */ +@RunWith(SpringJUnit4ClassRunner::class) +@ComponentScan("ru.sokomishalov.commons.distributed.locks") +@EnableAspectJAutoProxy +abstract class AbstractDistributedLockAspectTest { + + @Autowired + lateinit var someBean: AspectConfig.SomeBean + + @Test + fun `Check at least for duration`() { + val counter = AtomicInteger(0) + val iterations = 5 + + repeat(iterations) { + someBean.incrementCounterAtLeast(counter) + } + + assertEquals(1, counter.get()) + } + + @Test + fun `Check at most for duration`() { + val counter = AtomicInteger(0) + val iterations = 5 + + repeat(iterations) { + someBean.incrementCounterAtMost(counter) + } + + assertEquals(iterations, counter.get()) + } + + @TestConfiguration + open class AspectConfig { + + @Bean + open fun aspect(distributedLockProvider: DistributedLockProvider): WithDistributedLockAspect { + return WithDistributedLockAspect(distributedLockProvider) + } + + @Component + open class SomeBean { + + @WithDistributedLock(lockAtLeastForMs = 600_000, lockAtMostForMs = 600_000) + open fun incrementCounterAtLeast(counter: AtomicInteger) { + counter.incrementAndGet() + } + + @WithDistributedLock(lockAtMostForMs = 600_000) + open fun incrementCounterAtMost(counter: AtomicInteger) { + counter.incrementAndGet() + } + } + } +} \ No newline at end of file diff --git a/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/AbstractClusterLockTest.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/AbstractDistributedLockWithExtensionsTest.kt similarity index 77% rename from commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/AbstractClusterLockTest.kt rename to commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/AbstractDistributedLockWithExtensionsTest.kt index f59d6f3..76bfc1a 100644 --- a/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/AbstractClusterLockTest.kt +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/AbstractDistributedLockWithExtensionsTest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ru.sokomishalov.commons.spring.locks.cluster +package ru.sokomishalov.commons.distributed.locks import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -21,9 +21,12 @@ import org.junit.Test import java.time.Duration.ofMinutes import java.util.concurrent.atomic.AtomicInteger -abstract class AbstractClusterLockTest { +/** + * @author sokomishalov + */ +abstract class AbstractDistributedLockWithExtensionsTest { - protected abstract val provider: LockProvider + protected abstract val provider: DistributedLockProvider @Test fun `Check at least for duration`() { @@ -32,7 +35,7 @@ abstract class AbstractClusterLockTest { repeat(iterations) { runBlocking { - provider.withClusterLock(lockName = "atLeastForLock", lockAtLeastFor = ofMinutes(10)) { + provider.withDistributedLock(lockName = "lockAtLeastFor", lockAtLeastFor = ofMinutes(10)) { counter.incrementAndGet() } } @@ -48,7 +51,7 @@ abstract class AbstractClusterLockTest { repeat(iterations) { runBlocking { - provider.withClusterLock(lockName = "atLeastForLock", lockAtMostFor = ofMinutes(10)) { + provider.withDistributedLock(lockName = "lockAtMostFor", lockAtMostFor = ofMinutes(10)) { counter.incrementAndGet() } } diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoDistributedLockAspectTest.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoDistributedLockAspectTest.kt new file mode 100644 index 0000000..1801751 --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoDistributedLockAspectTest.kt @@ -0,0 +1,53 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks.mongo + +import org.junit.AfterClass +import org.junit.ClassRule +import org.springframework.context.ApplicationContextInitializer +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.test.context.ContextConfiguration +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.distributed.locks.AbstractDistributedLockAspectTest +import ru.sokomishalov.commons.util.MongoTestContainer +import ru.sokomishalov.commons.util.createDefaultMongoContainer +import ru.sokomishalov.commons.util.createReactiveMongoClient + + +/** + * @author sokomishalov + */ + +@ContextConfiguration(classes = [AbstractDistributedLockAspectTest.AspectConfig::class], initializers = [MongoDistributedLockAspectTest.Initializer::class]) +class MongoDistributedLockAspectTest : AbstractDistributedLockAspectTest() { + + companion object : Loggable { + @get:ClassRule + val mongo: MongoTestContainer = createDefaultMongoContainer() + + @AfterClass + @JvmStatic + fun stop() = mongo.stop() + } + + internal class Initializer : ApplicationContextInitializer { + override fun initialize(context: ConfigurableApplicationContext) { + mongo.start() + logInfo(mongo.logs) + context.beanFactory.registerSingleton("lockProvider", MongoReactiveDistributedLockProvider(client = mongo.createReactiveMongoClient())) + } + } +} \ No newline at end of file diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoDistributedLockProviderWithExtensionsTest.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoDistributedLockProviderWithExtensionsTest.kt new file mode 100644 index 0000000..fce6597 --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/mongo/MongoDistributedLockProviderWithExtensionsTest.kt @@ -0,0 +1,46 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks.mongo + +import org.junit.AfterClass +import org.junit.ClassRule +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.distributed.locks.AbstractDistributedLockWithExtensionsTest +import ru.sokomishalov.commons.distributed.locks.DistributedLockProvider +import ru.sokomishalov.commons.util.MongoTestContainer +import ru.sokomishalov.commons.util.createDefaultMongoContainer +import ru.sokomishalov.commons.util.createReactiveMongoClient + +/** + * @author sokomishalov + */ +class MongoDistributedLockProviderWithExtensionsTest : AbstractDistributedLockWithExtensionsTest() { + + companion object : Loggable { + @get:ClassRule + val mongo: MongoTestContainer = createDefaultMongoContainer() + + @AfterClass + @JvmStatic + fun stop() = mongo.stop() + } + + override val provider: DistributedLockProvider by lazy { + mongo.start() + logInfo(mongo.logs) + MongoReactiveDistributedLockProvider(client = mongo.createReactiveMongoClient()) + } +} \ No newline at end of file diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisDistributedLockAspectTest.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisDistributedLockAspectTest.kt new file mode 100644 index 0000000..192b710 --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisDistributedLockAspectTest.kt @@ -0,0 +1,57 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks.redis + +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisURI +import org.junit.AfterClass +import org.junit.ClassRule +import org.springframework.context.ApplicationContextInitializer +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.test.context.ContextConfiguration +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.distributed.locks.AbstractDistributedLockAspectTest +import ru.sokomishalov.commons.util.RedisTestContainer +import ru.sokomishalov.commons.util.createDefaultRedisContainer + + +/** + * @author sokomishalov + */ + +@ContextConfiguration(classes = [AbstractDistributedLockAspectTest.AspectConfig::class], initializers = [RedisDistributedLockAspectTest.Initializer::class]) +class RedisDistributedLockAspectTest : AbstractDistributedLockAspectTest() { + + companion object : Loggable { + @get:ClassRule + val redis: RedisTestContainer = createDefaultRedisContainer() + + @AfterClass + @JvmStatic + fun stop() = redis.stop() + } + + internal class Initializer : ApplicationContextInitializer { + override fun initialize(context: ConfigurableApplicationContext) { + redis.start() + logInfo(redis.logs) + context.beanFactory.registerSingleton("lockProvider", RedisLettuceDistributedLockProvider(client = RedisClient.create(RedisURI().apply { + host = redis.containerIpAddress + port = redis.firstMappedPort + }))) + } + } +} \ No newline at end of file diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisDistributedLockProviderWithExtensionsTest.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisDistributedLockProviderWithExtensionsTest.kt new file mode 100644 index 0000000..0dc8742 --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/distributed/locks/redis/RedisDistributedLockProviderWithExtensionsTest.kt @@ -0,0 +1,46 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.distributed.locks.redis + +import org.junit.AfterClass +import org.junit.ClassRule +import ru.sokomishalov.commons.core.log.Loggable +import ru.sokomishalov.commons.distributed.locks.AbstractDistributedLockWithExtensionsTest +import ru.sokomishalov.commons.distributed.locks.DistributedLockProvider +import ru.sokomishalov.commons.util.RedisTestContainer +import ru.sokomishalov.commons.util.createDefaultRedisContainer +import ru.sokomishalov.commons.util.createRedisClient + +/** + * @author sokomishalov + */ +class RedisDistributedLockProviderWithExtensionsTest : AbstractDistributedLockWithExtensionsTest() { + + companion object : Loggable { + @get:ClassRule + val redis: RedisTestContainer = createDefaultRedisContainer() + + @AfterClass + @JvmStatic + fun stop() = redis.stop() + } + + override val provider: DistributedLockProvider by lazy { + redis.start() + logInfo(redis.logs) + RedisLettuceDistributedLockProvider(client = redis.createRedisClient()) + } +} \ No newline at end of file diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/util/MongoTestContainer.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/util/MongoTestContainer.kt new file mode 100644 index 0000000..b53a196 --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/util/MongoTestContainer.kt @@ -0,0 +1,38 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.util + +import com.mongodb.ConnectionString +import com.mongodb.reactivestreams.client.MongoClient +import com.mongodb.reactivestreams.client.MongoClients +import org.testcontainers.containers.GenericContainer + +/** + * @author sokomishalov + */ + +class MongoTestContainer : GenericContainer("mvertes/alpine-mongo") + +internal fun createDefaultMongoContainer(): MongoTestContainer { + return MongoTestContainer().apply { + withReuse(true) + withExposedPorts(27017) + } +} + +fun MongoTestContainer.createReactiveMongoClient(): MongoClient { + return MongoClients.create(ConnectionString("mongodb://${containerIpAddress}:${firstMappedPort}")) +} \ No newline at end of file diff --git a/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/util/RedisTestContainer.kt b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/util/RedisTestContainer.kt new file mode 100644 index 0000000..60d67fb --- /dev/null +++ b/commons-distributed-locks/src/test/kotlin/ru/sokomishalov/commons/util/RedisTestContainer.kt @@ -0,0 +1,38 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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 ru.sokomishalov.commons.util + +import io.lettuce.core.RedisClient +import io.lettuce.core.RedisURI +import org.testcontainers.containers.GenericContainer +import java.time.Duration + +/** + * @author sokomishalov + */ + +class RedisTestContainer : GenericContainer("redis:alpine") + +internal fun createDefaultRedisContainer(): RedisTestContainer { + return RedisTestContainer().apply { + withReuse(true) + withExposedPorts(6379) + } +} + +fun RedisTestContainer.createRedisClient(timeout: Duration = Duration.ofSeconds(1)): RedisClient { + return RedisClient.create(RedisURI(containerIpAddress, firstMappedPort, timeout)) +} \ No newline at end of file diff --git a/commons-logging/pom.xml b/commons-logging/pom.xml new file mode 100644 index 0000000..7fbe623 --- /dev/null +++ b/commons-logging/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + + + ru.sokomishalov.commons + commons-parent + 1.1.0 + + + commons-logging + 1.1.0 + + + + ru.sokomishalov.commons + commons-core + 1.1.0 + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-classic + 1.2.3 + test + + + + org.jetbrains.kotlin + kotlin-test-junit + ${kotlin.version} + test + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + ${project.artifactId} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + -Xuse-experimental=kotlin.Experimental + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + kapt + + kapt + + + + ${project.basedir}/src/main/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/CustomLoggerFactory.kt b/commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/CustomLoggerFactory.kt similarity index 77% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/CustomLoggerFactory.kt rename to commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/CustomLoggerFactory.kt index 03901b0..08467c9 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/CustomLoggerFactory.kt +++ b/commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/CustomLoggerFactory.kt @@ -16,6 +16,7 @@ package ru.sokomishalov.commons.core.log import org.slf4j.Logger +import ru.sokomishalov.commons.core.reflection.unwrapCompanionClass import java.util.concurrent.ConcurrentHashMap /** @@ -26,12 +27,13 @@ object CustomLoggerFactory { private val loggersMap: MutableMap = ConcurrentHashMap() fun getLogger(clazz: Class): Logger { - val logger = loggersMap[clazz.name] + val nonCompanionClazz = clazz.unwrapCompanionClass() + val logger = loggersMap[nonCompanionClazz.name] return when { logger != null -> logger else -> { - val newLogger = loggerFor(clazz) - loggersMap[clazz.name] = newLogger + val newLogger = loggerFor(nonCompanionClazz) + loggersMap[nonCompanionClazz.name] = newLogger newLogger } } diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/LogUtils.kt b/commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/LogUtils.kt similarity index 95% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/LogUtils.kt rename to commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/LogUtils.kt index 7d3d0cf..392815f 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/LogUtils.kt +++ b/commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/LogUtils.kt @@ -31,5 +31,5 @@ fun loggerFor(name: String): Logger = getLogger(name) inline fun T.logger(): Logger = loggerFor(javaClass) -inline fun T.loggerDelegate(): Lazy = lazy { loggerFor(unwrapCompanionClass(javaClass)) } +inline fun T.loggerDelegate(): Lazy = lazy { loggerFor(javaClass.unwrapCompanionClass()) } diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/Loggable.kt b/commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/Loggable.kt similarity index 100% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/Loggable.kt rename to commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/Loggable.kt diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/Sl4jExtensions.kt b/commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/Sl4jExtensions.kt similarity index 100% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/log/Sl4jExtensions.kt rename to commons-logging/src/main/kotlin/ru/sokomishalov/commons/core/log/Sl4jExtensions.kt diff --git a/commons-core/src/test/kotlin/ru/sokomishalov/commons/core/log/LogUtilsTest.kt b/commons-logging/src/test/kotlin/ru/sokomishalov/commons/core/log/LogUtilsTest.kt similarity index 79% rename from commons-core/src/test/kotlin/ru/sokomishalov/commons/core/log/LogUtilsTest.kt rename to commons-logging/src/test/kotlin/ru/sokomishalov/commons/core/log/LogUtilsTest.kt index 7aff836..3591704 100644 --- a/commons-core/src/test/kotlin/ru/sokomishalov/commons/core/log/LogUtilsTest.kt +++ b/commons-logging/src/test/kotlin/ru/sokomishalov/commons/core/log/LogUtilsTest.kt @@ -26,6 +26,7 @@ class LogUtilsTest { @Test fun `Assert logger`() { LogWithInterface().doJob() + LogWithCompanion().doJob() LogWithDelegate().doJob() } @@ -33,16 +34,26 @@ class LogUtilsTest { class LogWithInterface : Loggable { fun doJob() { - log("test") + log("interface") } } -class LogWithDelegate { +class LogWithCompanion { companion object : Loggable fun doJob() { - logInfo { "kek" } - logger.debug("lek") + logInfo { "companion" } + } +} + +class LogWithDelegate { + + companion object { + private val log by loggerDelegate() + } + + fun doJob() { + log.info { "companion delegate" } } } \ No newline at end of file diff --git a/commons-reactor/pom.xml b/commons-reactor/pom.xml new file mode 100644 index 0000000..8f152e5 --- /dev/null +++ b/commons-reactor/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + + ru.sokomishalov.commons + commons-parent + 1.1.0 + + + commons-reactor + 1.1.0 + + + + ru.sokomishalov.commons + commons-core + 1.1.0 + + + + io.projectreactor + reactor-core + ${reactor.version} + + + io.projectreactor.netty + reactor-netty + ${reactor-netty.version} + + + + + ${project.basedir}/src/main/kotlin + ${project.artifactId} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + -Xuse-experimental=kotlin.Experimental + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + kapt + + kapt + + + + ${project.basedir}/src/main/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + + \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/http/HttpUtils.kt b/commons-reactor/src/main/kotlin/ru/sokomishalov/commons/core/http/HttpUtils.kt similarity index 100% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/http/HttpUtils.kt rename to commons-reactor/src/main/kotlin/ru/sokomishalov/commons/core/http/HttpUtils.kt diff --git a/commons-serialization/pom.xml b/commons-serialization/pom.xml new file mode 100644 index 0000000..98cca1a --- /dev/null +++ b/commons-serialization/pom.xml @@ -0,0 +1,144 @@ + + + 4.0.0 + + + ru.sokomishalov.commons + commons-parent + 1.1.0 + + + commons-serialization + 1.1.0 + + + + + + ru.sokomishalov.commons + commons-core + 1.1.0 + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.module + jackson-module-kotlin + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${jackson.version} + true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-protobuf + ${jackson.version} + true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-csv + ${jackson.version} + true + + + + + + ${project.basedir}/src/main/kotlin + ${project.artifactId} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + ${java.version} + + -Xuse-experimental=kotlin.Experimental + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + kapt + + kapt + + + + ${project.basedir}/src/main/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + + + \ No newline at end of file diff --git a/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/CsvObjectMapperHelper.kt b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/CsvObjectMapperHelper.kt new file mode 100644 index 0000000..e3e768e --- /dev/null +++ b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/CsvObjectMapperHelper.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("unused") + +package ru.sokomishalov.commons.core.serialization + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.csv.CsvFactory + +/** + * @author sokomishalov + */ + +val CSV_OBJECT_MAPPER: ObjectMapper = buildComplexObjectMapper(factory = CsvFactory()) \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockProvider.kt b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/JsonObjectMapperHelper.kt similarity index 76% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockProvider.kt rename to commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/JsonObjectMapperHelper.kt index bb59306..5606d9e 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/locks/cluster/LockProvider.kt +++ b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/JsonObjectMapperHelper.kt @@ -13,16 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:Suppress("unused") +package ru.sokomishalov.commons.core.serialization -package ru.sokomishalov.commons.spring.locks.cluster +import com.fasterxml.jackson.databind.ObjectMapper /** * @author sokomishalov */ -interface LockProvider { - suspend fun tryLock(lockInfo: LockInfo): Boolean - - suspend fun release(lockInfo: LockInfo) -} \ No newline at end of file +val OBJECT_MAPPER: ObjectMapper = buildComplexObjectMapper() \ No newline at end of file diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ObjectMapperHelper.kt b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ObjectMapperHelper.kt similarity index 97% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ObjectMapperHelper.kt rename to commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ObjectMapperHelper.kt index bff8e8c..87e59cf 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ObjectMapperHelper.kt +++ b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ObjectMapperHelper.kt @@ -34,7 +34,6 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule /** * @author sokomishalov */ -val OBJECT_MAPPER: ObjectMapper = buildComplexObjectMapper() fun buildComplexObjectMapper( factory: JsonFactory? = null, diff --git a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/serialization/CoroutinesSerialization.kt b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ProtobufObjectMapperHelper.kt similarity index 53% rename from commons-core/src/main/kotlin/ru/sokomishalov/commons/core/serialization/CoroutinesSerialization.kt rename to commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ProtobufObjectMapperHelper.kt index 6e14626..7613b68 100644 --- a/commons-core/src/main/kotlin/ru/sokomishalov/commons/core/serialization/CoroutinesSerialization.kt +++ b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/ProtobufObjectMapperHelper.kt @@ -13,30 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:Suppress("unused", "RemoveExplicitTypeArguments") +@file:Suppress("unused") package ru.sokomishalov.commons.core.serialization -import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.convertValue -import com.fasterxml.jackson.module.kotlin.readValue -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.withContext - +import com.fasterxml.jackson.dataformat.protobuf.ProtobufFactory /** * @author sokomishalov */ -suspend inline fun ObjectMapper.aReadTree(content: String): JsonNode = withContext(IO) { - readTree(content) -} - -suspend inline fun ObjectMapper.aReadValue(content: String): T = withContext(IO) { - readValue(content) -} - -suspend inline fun ObjectMapper.aConvertValue(from: String): T = withContext(IO) { - convertValue(from) -} \ No newline at end of file +val PROTOBUF_OBJECT_MAPPER: ObjectMapper = buildComplexObjectMapper(factory = ProtobufFactory()) \ No newline at end of file diff --git a/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/XmlObjectMapperHelper.kt b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/XmlObjectMapperHelper.kt new file mode 100644 index 0000000..97b6fc7 --- /dev/null +++ b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/XmlObjectMapperHelper.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("unused") + +package ru.sokomishalov.commons.core.serialization + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.xml.XmlFactory + +/** + * @author sokomishalov + */ + +val XML_OBJECT_MAPPER: ObjectMapper = buildComplexObjectMapper(factory = XmlFactory()) \ No newline at end of file diff --git a/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/YamlObjectMapperHelper.kt b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/YamlObjectMapperHelper.kt new file mode 100644 index 0000000..28cdf5e --- /dev/null +++ b/commons-serialization/src/main/kotlin/ru/sokomishalov/commons/core/serialization/YamlObjectMapperHelper.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("unused") + +package ru.sokomishalov.commons.core.serialization + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory + +/** + * @author sokomishalov + */ + +val YAML_OBJECT_MAPPER: ObjectMapper = buildComplexObjectMapper(factory = YAMLFactory()) \ No newline at end of file diff --git a/commons-spring/pom.xml b/commons-spring/pom.xml index 495ccfc..87d9556 100644 --- a/commons-spring/pom.xml +++ b/commons-spring/pom.xml @@ -7,25 +7,11 @@ ru.sokomishalov.commons commons-parent - 1.0.29 + 1.1.0 commons-spring - 1.0.29 - - - 1.3.61 - 2.2.2.RELEASE - 5.2.2.RELEASE - 2.8.0 - 2.9.2 - - 2.2.0 - 3.11.0 - 1.12.0 - 5.2.1.RELEASE - 0.7.2 - + 1.1.0 @@ -34,7 +20,31 @@ ru.sokomishalov.commons commons-core - 1.0.29 + 1.1.0 + + + + ru.sokomishalov.commons + commons-logging + 1.1.0 + + + + ru.sokomishalov.commons + commons-serialization + 1.1.0 + + + + ru.sokomishalov.commons + commons-reactor + 1.1.0 + + + + ru.sokomishalov.commons + commons-coroutines + 1.1.0 @@ -57,6 +67,20 @@ + + ru.sokomishalov.commons + commons-distributed-locks + 1.1.0 + true + + + + ru.sokomishalov.commons + commons-cache + 1.1.0 + true + + org.springframework spring-context-support @@ -95,41 +119,14 @@ io.lettuce lettuce-core - ${redis-lettuce.version} + ${lettuce.version} true - - - - org.springframework.boot - spring-boot-starter-test - ${spring.boot.version} - - - org.jetbrains.kotlin - kotlin-test-junit - ${kotlin.version} - test - - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - ${mongo-embedded.version} - test - - - it.ozimov - embedded-redis - ${redis-embedded.version} - test - - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/test/kotlin ${project.artifactId} diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/InMemoryCacheAutoConfiguration.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/InMemoryCacheAutoConfiguration.kt new file mode 100644 index 0000000..0a15d33 --- /dev/null +++ b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/InMemoryCacheAutoConfiguration.kt @@ -0,0 +1,47 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("SpringJavaInjectionPointsAutowiringInspection") + +package ru.sokomishalov.commons.spring.autoconfigure + +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.beans.factory.ObjectProvider +import org.springframework.boot.autoconfigure.AutoConfigureOrder +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.Ordered.LOWEST_PRECEDENCE +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.cache.inmemory.ConcurrentMapCacheService +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER + +/** + * @author sokomishalov + */ +@Configuration(proxyBeanMethods = false) +@AutoConfigureOrder(LOWEST_PRECEDENCE) +@ConditionalOnClass(CacheService::class) +class InMemoryCacheAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(CacheService::class) + fun concurrentMapCacheService( + mapper: ObjectProvider + ): CacheService { + return ConcurrentMapCacheService(mapper = mapper.getIfAvailable { OBJECT_MAPPER }) + } +} \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveCacheAutoConfiguration.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveCacheAutoConfiguration.kt new file mode 100644 index 0000000..9281ce0 --- /dev/null +++ b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveCacheAutoConfiguration.kt @@ -0,0 +1,51 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("SpringJavaInjectionPointsAutowiringInspection") + +package ru.sokomishalov.commons.spring.autoconfigure + +import com.fasterxml.jackson.databind.ObjectMapper +import com.mongodb.reactivestreams.client.MongoClient +import org.springframework.beans.factory.ObjectProvider +import org.springframework.boot.autoconfigure.AutoConfigureOrder +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.Ordered.HIGHEST_PRECEDENCE +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.cache.mongo.MongoCacheService +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER + +/** + * @author sokomishalov + */ +@Configuration +@ConditionalOnClass(CacheService::class, MongoClient::class) +@AutoConfigureOrder(HIGHEST_PRECEDENCE) +class MongoReactiveCacheAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(CacheService::class) + @ConditionalOnBean(MongoClient::class) + fun mongoCacheService( + client: MongoClient, + mapper: ObjectProvider + ): CacheService { + return MongoCacheService(client = client, mapper = mapper.getIfAvailable { OBJECT_MAPPER }) + } +} \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveAutoConfiguration.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveDistributedLocksAutoConfiguration.kt similarity index 64% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveAutoConfiguration.kt rename to commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveDistributedLocksAutoConfiguration.kt index f2e69ee..0796db4 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveAutoConfiguration.kt +++ b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/MongoReactiveDistributedLocksAutoConfiguration.kt @@ -18,25 +18,29 @@ package ru.sokomishalov.commons.spring.autoconfigure import com.mongodb.reactivestreams.client.MongoClient +import org.springframework.boot.autoconfigure.AutoConfigureOrder import org.springframework.boot.autoconfigure.condition.ConditionalOnBean import org.springframework.boot.autoconfigure.condition.ConditionalOnClass import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import ru.sokomishalov.commons.spring.locks.cluster.LockProvider -import ru.sokomishalov.commons.spring.locks.cluster.mongo.MongoReactiveLockProvider +import org.springframework.core.Ordered.HIGHEST_PRECEDENCE +import ru.sokomishalov.commons.distributed.locks.DistributedLockProvider +import ru.sokomishalov.commons.distributed.locks.mongo.MongoReactiveDistributedLockProvider /** * @author sokomishalov */ @Configuration -@ConditionalOnClass(MongoClient::class) -class MongoReactiveAutoConfiguration { +@ConditionalOnClass(DistributedLockProvider::class, MongoClient::class) +@AutoConfigureOrder(HIGHEST_PRECEDENCE) +class MongoReactiveDistributedLocksAutoConfiguration { @Bean - @ConditionalOnMissingBean(LockProvider::class) + @ConditionalOnMissingBean(DistributedLockProvider::class) @ConditionalOnBean(MongoClient::class) - fun reactiveMongoClusterLockProvider(client: MongoClient): LockProvider = - MongoReactiveLockProvider(client = client) + fun reactiveMongoDistributedLockProvider(client: MongoClient): DistributedLockProvider { + return MongoReactiveDistributedLockProvider(client = client) + } } \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceCacheAutoConfiguration.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceCacheAutoConfiguration.kt new file mode 100644 index 0000000..2ca3636 --- /dev/null +++ b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceCacheAutoConfiguration.kt @@ -0,0 +1,51 @@ +/** + * Copyright 2019-2019 the original author or authors. + * + * 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. + */ +@file:Suppress("SpringJavaInjectionPointsAutowiringInspection") + +package ru.sokomishalov.commons.spring.autoconfigure + +import com.fasterxml.jackson.databind.ObjectMapper +import io.lettuce.core.RedisClient +import org.springframework.beans.factory.ObjectProvider +import org.springframework.boot.autoconfigure.AutoConfigureOrder +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.Ordered.HIGHEST_PRECEDENCE +import ru.sokomishalov.commons.cache.CacheService +import ru.sokomishalov.commons.cache.redis.RedisCacheService +import ru.sokomishalov.commons.core.serialization.OBJECT_MAPPER + +/** + * @author sokomishalov + */ +@Configuration +@ConditionalOnClass(CacheService::class, RedisClient::class) +@AutoConfigureOrder(HIGHEST_PRECEDENCE) +class RedisLettuceCacheAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(CacheService::class) + @ConditionalOnBean(RedisClient::class) + fun redisCacheService( + client: RedisClient, + mapper: ObjectProvider + ): CacheService { + return RedisCacheService(client = client, mapper = mapper.getIfAvailable { OBJECT_MAPPER }) + } +} \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceAutoConfiguration.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceDistributedLocksAutoConfiguration.kt similarity index 64% rename from commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceAutoConfiguration.kt rename to commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceDistributedLocksAutoConfiguration.kt index 72e8525..bd45f9d 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceAutoConfiguration.kt +++ b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/autoconfigure/RedisLettuceDistributedLocksAutoConfiguration.kt @@ -18,24 +18,28 @@ package ru.sokomishalov.commons.spring.autoconfigure import io.lettuce.core.RedisClient +import org.springframework.boot.autoconfigure.AutoConfigureOrder import org.springframework.boot.autoconfigure.condition.ConditionalOnBean import org.springframework.boot.autoconfigure.condition.ConditionalOnClass import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import ru.sokomishalov.commons.spring.locks.cluster.LockProvider -import ru.sokomishalov.commons.spring.locks.cluster.redis.RedisLettuceLockProvider +import org.springframework.core.Ordered.HIGHEST_PRECEDENCE +import ru.sokomishalov.commons.distributed.locks.DistributedLockProvider +import ru.sokomishalov.commons.distributed.locks.redis.RedisLettuceDistributedLockProvider /** * @author sokomishalov */ @Configuration -@ConditionalOnClass(RedisClient::class) -class RedisLettuceAutoConfiguration { +@ConditionalOnClass(DistributedLockProvider::class, RedisClient::class) +@AutoConfigureOrder(HIGHEST_PRECEDENCE) +class RedisLettuceDistributedLocksAutoConfiguration { @Bean - @ConditionalOnMissingBean(LockProvider::class) + @ConditionalOnMissingBean(DistributedLockProvider::class) @ConditionalOnBean(RedisClient::class) - fun reactiveMongoClusterLockProvider(client: RedisClient): LockProvider = - RedisLettuceLockProvider(client = client) + fun redisDistributedLockProvider(client: RedisClient): DistributedLockProvider { + return RedisLettuceDistributedLockProvider(client = client) + } } \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/cache/CacheManagerService.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/cache/CacheManagerService.kt deleted file mode 100644 index b954538..0000000 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/cache/CacheManagerService.kt +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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. - */ -@file:Suppress("UNCHECKED_CAST", "unused") - -package ru.sokomishalov.commons.spring.cache - -import org.springframework.cache.Cache -import org.springframework.cache.CacheManager -import org.springframework.cache.concurrent.ConcurrentMapCacheManager -import ru.sokomishalov.commons.core.common.unit - - -/** - * @author sokomishalov - */ -class CacheManagerService( - private val caches: List = emptyList(), - private val cacheManager: CacheManager = ConcurrentMapCacheManager(*caches.toTypedArray()) -) : CacheService { - - private fun getCache(cacheName: String): Cache? = cacheManager.getCache(cacheName) - - override suspend fun get(cacheName: String, key: String): T? = getCache(cacheName)?.get(key)?.get() as T? - override suspend fun put(cacheName: String, key: String, value: T) = getCache(cacheName)?.put(key, value).unit() - override suspend fun evict(cacheName: String, key: String) = getCache(cacheName)?.evict(key).unit() -} \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/cache/CacheService.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/cache/CacheService.kt deleted file mode 100644 index 0a908e9..0000000 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/cache/CacheService.kt +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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. - */ -@file:Suppress("unused") - -package ru.sokomishalov.commons.spring.cache - -/** - * @author sokomishalov - */ -interface CacheService { - - companion object { - const val DEFAULT_CACHE = "DEFAULT" - } - - suspend fun get(cacheName: String = DEFAULT_CACHE, key: String): T? - - suspend fun put(cacheName: String = DEFAULT_CACHE, key: String, value: T) - - suspend fun evict(cacheName: String = DEFAULT_CACHE, key: String) - - suspend fun get(cacheName: String = DEFAULT_CACHE, key: String, orElse: suspend () -> T): T { - val value = get(cacheName, key) - - return when { - value != null -> value - else -> { - val orElseValue = orElse() - put(cacheName, key, orElseValue) - orElseValue - } - } - } -} \ No newline at end of file diff --git a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/net/NetUtils.kt b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/net/NetUtils.kt index 99e3819..ee5edd4 100644 --- a/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/net/NetUtils.kt +++ b/commons-spring/src/main/kotlin/ru/sokomishalov/commons/spring/net/NetUtils.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("unused") + package ru.sokomishalov.commons.spring.net import org.springframework.util.SocketUtils.* diff --git a/commons-spring/src/main/resources/META-INF/spring.factories b/commons-spring/src/main/resources/META-INF/spring.factories index 5166a17..23351ba 100644 --- a/commons-spring/src/main/resources/META-INF/spring.factories +++ b/commons-spring/src/main/resources/META-INF/spring.factories @@ -1,3 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - ru.sokomishalov.commons.spring.autoconfigure.MongoReactiveAutoConfiguration,\ - ru.sokomishalov.commons.spring.autoconfigure.RedisLettuceAutoConfiguration \ No newline at end of file + ru.sokomishalov.commons.spring.autoconfigure.InMemoryCacheAutoConfiguration,\ + ru.sokomishalov.commons.spring.autoconfigure.MongoReactiveCacheAutoConfiguration,\ + ru.sokomishalov.commons.spring.autoconfigure.MongoReactiveDistributedLocksAutoConfiguration,\ + ru.sokomishalov.commons.spring.autoconfigure.RedisLettuceCacheAutoConfiguration,\ + ru.sokomishalov.commons.spring.autoconfigure.RedisLettuceDistributedLocksAutoConfiguration \ No newline at end of file diff --git a/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/mongo/MongoClusterLockTest.kt b/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/mongo/MongoClusterLockTest.kt deleted file mode 100644 index c125d9b..0000000 --- a/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/mongo/MongoClusterLockTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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 ru.sokomishalov.commons.spring.locks.cluster.mongo - -import com.mongodb.reactivestreams.client.MongoClients -import de.flapdoodle.embed.mongo.MongodProcess -import de.flapdoodle.embed.mongo.MongodStarter -import de.flapdoodle.embed.mongo.config.MongodConfigBuilder -import de.flapdoodle.embed.mongo.config.Net -import de.flapdoodle.embed.mongo.distribution.Version.Main.PRODUCTION -import de.flapdoodle.embed.process.runtime.Network.localhostIsIPv6 -import org.junit.After -import org.junit.Before -import ru.sokomishalov.commons.core.consts.LOCALHOST -import ru.sokomishalov.commons.spring.locks.cluster.AbstractClusterLockTest -import ru.sokomishalov.commons.spring.locks.cluster.LockProvider -import ru.sokomishalov.commons.spring.net.randomFreePort - - -/** - * @author sokomishalov - */ -class MongoClusterLockTest : AbstractClusterLockTest() { - - companion object { - const val DATABASE = "admin" - } - - private lateinit var mongoDaemon: MongodProcess - private lateinit var client: LockProvider - - @Before - fun setUp() { - val randomPort = randomFreePort() - - mongoDaemon = MongodStarter - .getDefaultInstance() - .prepare(MongodConfigBuilder() - .version(PRODUCTION) - .net(Net(LOCALHOST, randomPort, localhostIsIPv6())) - .build() - ) - .start() - - client = MongoReactiveLockProvider(client = MongoClients.create("mongodb://$LOCALHOST:$randomPort/$DATABASE")) - } - - - override val provider: LockProvider - get() = client - - @After - fun tearDown() { - mongoDaemon.stop() - } -} \ No newline at end of file diff --git a/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/redis/RedisClusterLockTest.kt b/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/redis/RedisClusterLockTest.kt deleted file mode 100644 index d9dfade..0000000 --- a/commons-spring/src/test/kotlin/ru/sokomishalov/commons/spring/locks/cluster/redis/RedisClusterLockTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2019-2019 the original author or authors. - * - * 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 ru.sokomishalov.commons.spring.locks.cluster.redis - -import io.lettuce.core.RedisClient -import io.lettuce.core.RedisURI -import org.junit.After -import org.junit.Before -import redis.embedded.RedisServer -import ru.sokomishalov.commons.core.consts.LOCALHOST -import ru.sokomishalov.commons.spring.locks.cluster.AbstractClusterLockTest -import ru.sokomishalov.commons.spring.locks.cluster.LockProvider -import ru.sokomishalov.commons.spring.net.randomFreePort - -class RedisClusterLockTest : AbstractClusterLockTest() { - - private lateinit var redisServer: RedisServer - private lateinit var lettuceLockProvider: RedisLettuceLockProvider - - @Before - fun setUp() { - val randomPort = randomFreePort() - redisServer = RedisServer(randomPort) - redisServer.start() - - lettuceLockProvider = RedisLettuceLockProvider(RedisClient.create(RedisURI.create(LOCALHOST, randomPort))) - } - - override val provider: LockProvider - get() = lettuceLockProvider - - @After - fun tearDown() { - redisServer.stop() - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1046624..8eae4cb 100644 --- a/pom.xml +++ b/pom.xml @@ -6,16 +6,36 @@ ru.sokomishalov.commons commons-parent - 1.0.29 + 1.1.0 pom 1.8 - 1.3.50 + 1.3.61 + 1.3.2 + 2.10.1 + 3.3.1.RELEASE + 0.9.2.RELEASE + 1.7.29 + 1.2.3 + 2.2.2.RELEASE + 5.2.2.RELEASE + 5.2.1.RELEASE + 1.9.5 + 2.8.0 + 2.9.2 + 1.13.0 + 1.12.3 commons-core + commons-logging + commons-serialization + commons-reactor + commons-coroutines + commons-distributed-locks + commons-cache commons-spring