From 3ec053ecda5164260cc7f905ca12db4c14517fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=B4=80=C9=B4=E1=B4=9B=E1=B4=8F=C9=B4?= Date: Mon, 7 Aug 2023 21:11:54 +0300 Subject: [PATCH] `@Cache` annotation and processors allowing generate typed cache implementations (#153) --- cache/cache-annotation-processor/build.gradle | 7 +- .../processor/CacheAnnotationProcessor.java | 289 +++++++++++++ .../CacheKeyAnnotationProcessor.java | 180 -------- .../cache/annotation/processor/CacheMeta.java | 62 --- .../annotation/processor/CacheMetaUtils.java | 194 --------- .../annotation/processor/CacheOperation.java | 57 ++- .../processor/CacheOperationManager.java | 109 ----- .../processor/CacheOperationUtils.java | 158 +++++++ .../processor/aop/AbstractAopCacheAspect.java | 65 +-- .../aop/CacheInvalidateAopKoraAspect.java | 107 +++-- .../processor/aop/CachePutAopKoraAspect.java | 122 ++++-- .../processor/aop/CacheableAopKoraAspect.java | 158 ++++--- .../javax.annotation.processing.Processor | 2 +- .../CacheAnnotationProcessorTests.java | 84 ++++ .../CacheKeyAnnotationProcessorTests.java | 104 ----- .../annotation/processor/CacheRunner.java | 33 ++ .../processor/MonoCacheAopTests.java | 71 +++- .../processor/MonoCacheOneAopTests.java | 178 ++++++++ .../processor/MonoManyCacheAopTests.java | 98 +++-- .../processor/MonoManyManyCacheAopTests.java | 248 ----------- .../processor/SyncCacheAopTests.java | 71 +++- .../processor/SyncCacheOneAopTests.java | 177 ++++++++ .../processor/SyncManyCacheAopTests.java | 97 +++-- .../processor/testcache/DummyCache.java | 86 ---- .../processor/testcache/DummyCache1.java | 9 + .../processor/testcache/DummyCache2.java | 12 + .../processor/testcache/DummyCache22.java | 12 + .../testcache/DummyCacheManager.java | 21 - ...etFlux.java => CacheableFluxWrongGet.java} | 8 +- ...utFlux.java => CacheableWrongFluxPut.java} | 8 +- ...ableTargetMono.java => CacheableMono.java} | 12 +- ...etMonoMany.java => CacheableMonoMany.java} | 21 +- .../reactive/mono/CacheableMonoOne.java | 34 ++ ...id.java => CacheableMonoWrongGetVoid.java} | 8 +- ...id.java => CacheableMonoWrongPutVoid.java} | 8 +- .../mono/CacheableTargetMonoManyMany.java | 42 -- ...r.java => CacheableWrongPublisherGet.java} | 8 +- ...r.java => CacheableWrongPublisherPut.java} | 8 +- ...ableTargetSync.java => CacheableSync.java} | 12 +- .../testdata/sync/CacheableSyncMany.java | 38 ++ .../testdata/sync/CacheableSyncOne.java | 33 ++ ... => CacheableSyncWrongAnnotationMany.java} | 8 +- ...=> CacheableSyncWrongArgumentMissing.java} | 8 +- ...a => CacheableSyncWrongArgumentOrder.java} | 8 +- ...va => CacheableSyncWrongArgumentType.java} | 8 +- ...id.java => CacheableSyncWrongGetVoid.java} | 8 +- .../testdata/sync/CacheableSyncWrongName.java | 9 + ...id.java => CacheableSyncWrongPutVoid.java} | 8 +- .../sync/CacheableTargetNameInvalid.java | 16 - .../sync/CacheableTargetSyncMany.java | 37 -- cache/cache-caffeine/build.gradle | 4 + .../cache/caffeine/AbstractCaffeineCache.java | 196 +++++++++ .../kora/cache/caffeine/CaffeineCache.java | 101 +---- .../cache/caffeine/CaffeineCacheConfig.java | 38 +- .../cache/caffeine/CaffeineCacheFactory.java | 17 +- .../cache/caffeine/CaffeineCacheManager.java | 37 -- .../cache/caffeine/CaffeineCacheModule.java | 54 ++- .../caffeine/CaffeineCacheTelemetry.java | 130 ++++++ .../caffeine/DefaultCaffeineCacheModule.java | 17 - .../kora/cache/caffeine/CacheRunner.java | 58 ++- .../cache/caffeine/MonoCacheAopTests.java | 159 ------- .../kora/cache/caffeine/MonoCacheTests.java | 84 ++++ .../cache/caffeine/SyncCacheAopTests.java | 158 ------- .../kora/cache/caffeine/SyncCacheTests.java | 84 ++++ .../caffeine/testdata/AppWithConfig.java | 30 -- .../testdata/CacheableMockLifecycle.java | 5 - .../testdata/CacheableTargetMono.java | 38 -- .../testdata/CacheableTargetSync.java | 37 -- .../cache/caffeine/testdata/DummyCache.java | 13 + .../java/ru/tinkoff/kora/cache/Cache.java | 70 ++- .../ru/tinkoff/kora/cache/CacheBuilder.java | 17 + .../java/ru/tinkoff/kora/cache/CacheKey.java | 299 ++++++++++++- .../ru/tinkoff/kora/cache/CacheKeyImpl.java | 71 ++++ .../ru/tinkoff/kora/cache/CacheLoader.java | 126 +++--- .../tinkoff/kora/cache/CacheLoaderImpls.java | 171 ++++++++ .../ru/tinkoff/kora/cache/CacheManager.java | 33 -- .../kora/cache/DefaultLoadableCache.java | 43 -- .../kora/cache/FacadeCacheBuilder.java | 294 +++++++++++++ .../kora/cache/FacadeCacheManagerBuilder.java | 175 -------- .../ru/tinkoff/kora/cache/LoadableCache.java | 61 +-- .../tinkoff/kora/cache/LoadableCacheImpl.java | 43 ++ .../kora/cache/LoadableCacheManager.java | 15 - .../tinkoff/kora/cache/annotation/Cache.java | 16 + .../cache/annotation/CacheInvalidate.java | 10 +- .../cache/annotation/CacheInvalidates.java | 2 +- .../kora/cache/annotation/CachePut.java | 10 +- .../kora/cache/annotation/CachePuts.java | 2 +- .../kora/cache/annotation/Cacheable.java | 12 +- .../kora/cache/annotation/Cacheables.java | 2 +- .../kora/cache/telemetry/CacheMetrics.java | 4 +- .../kora/cache/telemetry/CacheTelemetry.java | 30 -- .../telemetry/CacheTelemetryOperation.java | 14 + .../kora/cache/telemetry/CacheTracer.java | 2 +- .../telemetry/DefaultCacheTelemetry.java | 95 ----- .../tinkoff/kora/cache/MonoCacheAopTests.java | 126 +++--- .../tinkoff/kora/cache/SyncCacheAopTests.java | 126 +++--- .../kora/cache/testcache/DummyCache.java | 89 +++- .../cache/testcache/DummyCacheManager.java | 23 - cache/cache-redis/build.gradle | 4 +- .../kora/cache/redis/AbstractRedisCache.java | 401 ++++++++++++++++++ .../cache/redis/DefaultRedisCacheModule.java | 22 - .../tinkoff/kora/cache/redis/RedisCache.java | 226 +--------- .../kora/cache/redis/RedisCacheConfig.java | 30 +- ...eyMapper.java => RedisCacheKeyMapper.java} | 2 +- .../kora/cache/redis/RedisCacheManager.java | 54 --- .../kora/cache/redis/RedisCacheModule.java | 174 ++++++-- .../kora/cache/redis/RedisCacheTelemetry.java | 130 ++++++ ...Mapper.java => RedisCacheValueMapper.java} | 2 +- .../redis/client/LettuceClientConfig.java | 2 +- .../redis/client/LettuceClientFactory.java | 2 + .../cache/redis/client/LettuceCommander.java | 4 +- .../cache/redis/client/LettuceModule.java | 3 + .../client/LettuceReactiveRedisClient.java | 35 +- .../redis/client/LettuceSyncRedisClient.java | 26 +- .../redis/client/ReactiveRedisClient.java | 16 +- .../cache/redis/client/SyncRedisClient.java | 10 +- .../tinkoff/kora/cache/redis/CacheRunner.java | 64 ++- .../kora/cache/redis/MonoCacheAopTests.java | 187 -------- .../kora/cache/redis/MonoCacheTests.java | 91 ++++ .../kora/cache/redis/SyncCacheAopTests.java | 185 -------- .../kora/cache/redis/SyncCacheTests.java | 91 ++++ .../cache/redis/testdata/AppWithConfig.java | 30 -- .../kora/cache/redis/testdata/Box.java | 5 - .../testdata/CacheableMockLifecycle.java | 7 - .../redis/testdata/CacheableTargetMono.java | 36 -- .../redis/testdata/CacheableTargetSync.java | 35 -- .../kora/cache/redis/testdata/DummyCache.java | 17 + cache/cache-symbol-processor/build.gradle | 5 +- .../processor/CacheKeySymbolProcessor.kt | 137 ------ .../kora/cache/symbol/processor/CacheMeta.kt | 30 -- .../cache/symbol/processor/CacheOperation.kt | 24 +- .../symbol/processor/CacheOperationManager.kt | 249 ----------- .../symbol/processor/CacheOperationUtils.kt | 191 +++++++++ .../symbol/processor/CacheSymbolProcessor.kt | 283 ++++++++++++ ...der.kt => CacheSymbolProcessorProvider.kt} | 5 +- .../processor/aop/AbstractAopCacheAspect.kt | 109 +---- .../aop/CacheInvalidateAopKoraAspect.kt | 65 ++- .../processor/aop/CachePutAopKoraAspect.kt | 53 ++- .../processor/aop/CacheableAopKoraAspect.kt | 106 ++++- ...ols.ksp.processing.SymbolProcessorProvider | 2 +- .../processor/CacheKeySymbolProcessorTests.kt | 140 ------ .../cache/symbol/processor/CacheRunner.kt | 26 ++ .../processor/CacheSymbolProcessorTests.kt | 103 +++++ .../symbol/processor/SuspendCacheAopTests.kt | 83 ++-- .../processor/SuspendCacheOneAopTests.kt | 169 ++++++++ .../processor/SuspendManyCacheAopTests.kt | 101 +++-- .../symbol/processor/SyncCacheAopTests.kt | 82 ++-- .../symbol/processor/SyncCacheOneAopTests.kt | 168 ++++++++ .../symbol/processor/SyncManyCacheAopTests.kt | 103 ++--- .../symbol/processor/testcache/DummyCache.kt | 62 --- .../symbol/processor/testcache/DummyCache1.kt | 8 + .../symbol/processor/testcache/DummyCache2.kt | 9 + .../processor/testcache/DummyCache22.kt | 9 + .../processor/testcache/DummyCacheManager.kt | 18 - ...Missing.kt => CacheableArgumentMissing.kt} | 8 +- ...Type.kt => CacheableArgumentWrongOrder.kt} | 8 +- ...Order.kt => CacheableArgumentWrongType.kt} | 8 +- ...leTargetGetVoid.kt => CacheableGetVoid.kt} | 8 +- ...NameInvalid.kt => CacheableNameInvalid.kt} | 6 +- ...leTargetPutVoid.kt => CacheablePutVoid.kt} | 8 +- ...acheableTargetSync.kt => CacheableSync.kt} | 12 +- .../processor/testdata/CacheableSyncMany.kt | 34 ++ .../processor/testdata/CacheableSyncOne.kt | 29 ++ .../testdata/CacheableTargetSyncMany.kt | 33 -- ...leTargetGetFlux.kt => CacheableGetFlux.kt} | 8 +- ...leTargetPutFlux.kt => CacheablePutFlux.kt} | 8 +- ...leTargetGetMono.kt => CacheableGetMono.kt} | 9 +- ...leTargetPutMono.kt => CacheablePutMono.kt} | 9 +- ...tPublisher.kt => CacheableGetPublisher.kt} | 8 +- ...tPublisher.kt => CacheablePutPublisher.kt} | 8 +- ...leTargetSuspend.kt => CacheableSuspend.kt} | 12 +- .../suspended/CacheableSuspendMany.kt | 39 ++ .../testdata/suspended/CacheableSuspendOne.kt | 34 ++ .../suspended/CacheableTargetSuspendMany.kt | 38 -- database/database-common/build.gradle | 3 +- dependencies.gradle | 4 +- micrometer/micrometer-module/build.gradle | 2 + .../kora/micrometer/module/MetricsModule.java | 11 + .../module/cache/MicrometerCacheMetrics.java | 15 +- mkdocs/docs/features/cache.md | 203 +++++---- .../cache/OpentelementryCacheTracer.java | 6 +- resilient/resilient-kora/build.gradle | 4 +- .../resilient-symbol-processor/build.gradle | 17 +- .../build.gradle | 5 +- validation/validation-common/build.gradle | 7 +- .../validation-symbol-processor/build.gradle | 17 +- 186 files changed, 6214 insertions(+), 5040 deletions(-) create mode 100644 cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessor.java delete mode 100644 cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessor.java delete mode 100644 cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMeta.java delete mode 100644 cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMetaUtils.java delete mode 100644 cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationManager.java create mode 100644 cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationUtils.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessorTests.java delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessorTests.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheRunner.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheOneAopTests.java delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyManyCacheAopTests.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheOneAopTests.java delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache1.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache2.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache22.java delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCacheManager.java rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/{CacheableTargetGetFlux.java => CacheableFluxWrongGet.java} (73%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/{CacheableTargetPutFlux.java => CacheableWrongFluxPut.java} (70%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/{CacheableTargetMono.java => CacheableMono.java} (67%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/{CacheableTargetMonoMany.java => CacheableMonoMany.java} (51%) create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoOne.java rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/{CacheableTargetGetMonoVoid.java => CacheableMonoWrongGetVoid.java} (73%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/{CacheableTargetPutMonoVoid.java => CacheableMonoWrongPutVoid.java} (70%) delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoManyMany.java rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/{CacheableTargetGetPublisher.java => CacheableWrongPublisherGet.java} (74%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/{CacheableTargetPutPublisher.java => CacheableWrongPublisherPut.java} (71%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetSync.java => CacheableSync.java} (62%) create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncMany.java create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncOne.java rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetAnnotationMultiple.java => CacheableSyncWrongAnnotationMany.java} (65%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetArgumentMissing.java => CacheableSyncWrongArgumentMissing.java} (68%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetArgumentWrongType.java => CacheableSyncWrongArgumentOrder.java} (67%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetArgumentWrongOrder.java => CacheableSyncWrongArgumentType.java} (67%) rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetGetVoid.java => CacheableSyncWrongGetVoid.java} (70%) create mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongName.java rename cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/{CacheableTargetPutVoid.java => CacheableSyncWrongPutVoid.java} (67%) delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetNameInvalid.java delete mode 100644 cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSyncMany.java create mode 100644 cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/AbstractCaffeineCache.java delete mode 100644 cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheManager.java create mode 100644 cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheTelemetry.java delete mode 100644 cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/DefaultCaffeineCacheModule.java delete mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheAopTests.java create mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheTests.java delete mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheAopTests.java create mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheTests.java delete mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/AppWithConfig.java delete mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableMockLifecycle.java delete mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetMono.java delete mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetSync.java create mode 100644 cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/DummyCache.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheBuilder.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKeyImpl.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoaderImpls.java delete mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheManager.java delete mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/DefaultLoadableCache.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheBuilder.java delete mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheManagerBuilder.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheImpl.java delete mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheManager.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cache.java delete mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetry.java create mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetryOperation.java delete mode 100644 cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/DefaultCacheTelemetry.java delete mode 100644 cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCacheManager.java create mode 100644 cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/AbstractRedisCache.java delete mode 100644 cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/DefaultRedisCacheModule.java rename cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/{RedisKeyMapper.java => RedisCacheKeyMapper.java} (78%) delete mode 100644 cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheManager.java create mode 100644 cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheTelemetry.java rename cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/{RedisValueMapper.java => RedisCacheValueMapper.java} (88%) delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheAopTests.java create mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheTests.java delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheAopTests.java create mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheTests.java delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/AppWithConfig.java delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/Box.java delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableMockLifecycle.java delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetMono.java delete mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetSync.java create mode 100644 cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/DummyCache.java delete mode 100644 cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessor.kt delete mode 100644 cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheMeta.kt delete mode 100644 cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationManager.kt create mode 100644 cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationUtils.kt create mode 100644 cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessor.kt rename cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/{CacheKeySymbolProcessorProvider.kt => CacheSymbolProcessorProvider.kt} (71%) delete mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorTests.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheRunner.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorTests.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheOneAopTests.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheOneAopTests.kt delete mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache1.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache2.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache22.kt delete mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCacheManager.kt rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetArgumentMissing.kt => CacheableArgumentMissing.kt} (67%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetArgumentWrongType.kt => CacheableArgumentWrongOrder.kt} (67%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetArgumentWrongOrder.kt => CacheableArgumentWrongType.kt} (67%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetGetVoid.kt => CacheableGetVoid.kt} (69%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetNameInvalid.kt => CacheableNameInvalid.kt} (73%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetPutVoid.kt => CacheablePutVoid.kt} (66%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/{CacheableTargetSync.kt => CacheableSync.kt} (61%) create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncMany.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncOne.kt delete mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSyncMany.kt rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/{CacheableTargetGetFlux.kt => CacheableGetFlux.kt} (73%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/{CacheableTargetPutFlux.kt => CacheablePutFlux.kt} (70%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/{CacheableTargetGetMono.kt => CacheableGetMono.kt} (70%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/{CacheableTargetPutMono.kt => CacheablePutMono.kt} (67%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/{CacheableTargetGetPublisher.kt => CacheableGetPublisher.kt} (74%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/{CacheableTargetPutPublisher.kt => CacheablePutPublisher.kt} (71%) rename cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/{CacheableTargetSuspend.kt => CacheableSuspend.kt} (65%) create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendMany.kt create mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendOne.kt delete mode 100644 cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspendMany.kt diff --git a/cache/cache-annotation-processor/build.gradle b/cache/cache-annotation-processor/build.gradle index f58e27d90..36261aebd 100644 --- a/cache/cache-annotation-processor/build.gradle +++ b/cache/cache-annotation-processor/build.gradle @@ -1,16 +1,13 @@ dependencies { - implementation project(":common") - implementation project(":cache:cache-common") - - implementation project(":annotation-processor-common") implementation project(":aop:aop-annotation-processor") implementation libs.javapoet implementation libs.logback.classic + testImplementation libs.prometheus.collector.caffeine testImplementation testFixtures(project(":annotation-processor-common")) testImplementation project(":internal:test-logging") - testImplementation "com.google.testing.compile:compile-testing:0.19" + testImplementation project(":cache:cache-caffeine") } apply from: '../../in-test-generated.gradle' diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessor.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessor.java new file mode 100644 index 000000000..3a75a7349 --- /dev/null +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessor.java @@ -0,0 +1,289 @@ +package ru.tinkoff.kora.cache.annotation.processor; + +import com.squareup.javapoet.*; +import ru.tinkoff.kora.annotation.processor.common.AbstractKoraProcessor; +import ru.tinkoff.kora.annotation.processor.common.CommonClassNames; +import ru.tinkoff.kora.common.Module; +import ru.tinkoff.kora.common.Tag; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Pattern; + +public class CacheAnnotationProcessor extends AbstractKoraProcessor { + + private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z][0-9a-zA-Z_]*"); + + private static final ClassName ANNOTATION_CACHE = ClassName.get("ru.tinkoff.kora.cache.annotation", "Cache"); + + private static final ClassName CAFFEINE_TELEMETRY = ClassName.get("ru.tinkoff.kora.cache.caffeine", "CaffeineCacheTelemetry"); + private static final ClassName REDIS_TELEMETRY = ClassName.get("ru.tinkoff.kora.cache.redis", "RedisCacheTelemetry"); + private static final ClassName CLASS_CONFIG = ClassName.get("ru.tinkoff.kora.config.common", "Config"); + private static final ClassName CLASS_CONFIG_EXTRACTOR = ClassName.get("ru.tinkoff.kora.config.common.extractor", "ConfigValueExtractor"); + + private static final ClassName CAFFEINE_CACHE = ClassName.get("ru.tinkoff.kora.cache.caffeine", "CaffeineCache"); + private static final ClassName CAFFEINE_CACHE_FACTORY = ClassName.get("ru.tinkoff.kora.cache.caffeine", "CaffeineCacheFactory"); + private static final ClassName CAFFEINE_CACHE_CONFIG = ClassName.get("ru.tinkoff.kora.cache.caffeine", "CaffeineCacheConfig"); + private static final ClassName CAFFEINE_CACHE_IMPL = ClassName.get("ru.tinkoff.kora.cache.caffeine", "AbstractCaffeineCache"); + + private static final ClassName REDIS_CACHE = ClassName.get("ru.tinkoff.kora.cache.redis", "RedisCache"); + private static final ClassName REDIS_CACHE_IMPL = ClassName.get("ru.tinkoff.kora.cache.redis", "AbstractRedisCache"); + private static final ClassName REDIS_CACHE_CONFIG = ClassName.get("ru.tinkoff.kora.cache.redis", "RedisCacheConfig"); + private static final ClassName REDIS_CACHE_CLIENT_SYNC = ClassName.get("ru.tinkoff.kora.cache.redis", "SyncRedisClient"); + private static final ClassName REDIS_CACHE_CLIENT_REACTIVE = ClassName.get("ru.tinkoff.kora.cache.redis", "ReactiveRedisClient"); + private static final ClassName REDIS_CACHE_MAPPER_KEY = ClassName.get("ru.tinkoff.kora.cache.redis", "RedisCacheKeyMapper"); + private static final ClassName REDIS_CACHE_MAPPER_VALUE = ClassName.get("ru.tinkoff.kora.cache.redis", "RedisCacheValueMapper"); + + private static Set getSupportedAnnotations() { + return Set.of(ANNOTATION_CACHE.canonicalName()); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + } + + @Override + public Set getSupportedAnnotationTypes() { + return getSupportedAnnotations(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + final List annotatedElements = getAnnotatedElements(roundEnv); + for (TypeElement cacheContract : annotatedElements) { + try { + if (!cacheContract.getKind().isInterface()) { + throw new IllegalArgumentException("@Cache annotation is intended to be used on interfaces, but was: " + cacheContract.getKind().name()); + } + + final Optional cacheContractType = getCacheSuperType(cacheContract); + if (cacheContractType.isEmpty()) { + throw new IllegalArgumentException("@Cache is expected to be known super type " + + CAFFEINE_CACHE.canonicalName() + + " or " + + REDIS_CACHE.canonicalName() + + ", but was: " + cacheContract.getSuperclass()); + } + + final String packageName = getPackage(cacheContract); + final ClassName cacheImplName = ClassName.get(cacheContract); + + var cacheImplBase = getCacheImplBase(cacheContract, cacheContractType.get()); + var implSpec = TypeSpec.classBuilder(getCacheImpl(cacheContract)) + .addModifiers(Modifier.FINAL) + .addAnnotation(AnnotationSpec.builder(CommonClassNames.koraGenerated) + .addMember("value", CodeBlock.of("$S", CacheAnnotationProcessor.class.getCanonicalName())).build()) + .addMethod(getCacheConstructor(cacheContract, cacheContractType.get())) + .superclass(cacheImplBase) + .addSuperinterface(cacheContract.asType()) + .build(); + + final JavaFile implFile = JavaFile.builder(cacheImplName.packageName(), implSpec).build(); + implFile.writeTo(processingEnv.getFiler()); + + var moduleSpec = TypeSpec.interfaceBuilder(ClassName.get(packageName, "$%sModule".formatted(cacheImplName.simpleName()))) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(AnnotationSpec.builder(CommonClassNames.koraGenerated) + .addMember("value", CodeBlock.of("$S", CacheAnnotationProcessor.class.getCanonicalName())).build()) + .addAnnotation(Module.class) + .addMethod(getCacheMethodImpl(cacheContract, cacheContractType.get())) + .addMethod(getCacheMethodConfig(cacheContract, cacheContractType.get())) + .build(); + + final JavaFile moduleFile = JavaFile.builder(cacheImplName.packageName(), moduleSpec).build(); + moduleFile.writeTo(processingEnv.getFiler()); + } catch (IOException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), cacheContract); + e.printStackTrace(); + return false; + } catch (IllegalArgumentException | IllegalStateException e) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), cacheContract); + return false; + } + } + + return false; + } + + private Optional getCacheSuperType(TypeElement candidate) { + final TypeElement caffeineElement = elements.getTypeElement(CAFFEINE_CACHE.canonicalName()); + if (caffeineElement != null) { + return types.directSupertypes(candidate.asType()).stream() + .filter(t -> t instanceof DeclaredType) + .map(t -> ((DeclaredType) t)) + .filter(t -> types.isAssignable(t.asElement().asType(), caffeineElement.asType())) + .findFirst(); + } + + final TypeElement redisElement = elements.getTypeElement(REDIS_CACHE.canonicalName()); + if (redisElement != null) { + return types.directSupertypes(candidate.asType()).stream() + .filter(t -> t instanceof DeclaredType) + .map(t -> ((DeclaredType) t)) + .filter(t -> types.isAssignable(t.asElement().asType(), redisElement.asType())) + .findFirst(); + } + + return Optional.empty(); + } + + private TypeName getCacheImplBase(TypeElement cacheContract, DeclaredType cacheType) { + final ClassName impl; + if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(CAFFEINE_CACHE.canonicalName())) { + impl = CAFFEINE_CACHE_IMPL; + } else if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(REDIS_CACHE.canonicalName())) { + impl = REDIS_CACHE_IMPL; + } else { + throw new UnsupportedOperationException("Unknown implementation: " + cacheContract.getQualifiedName()); + } + + return TypeName.get(types.getDeclaredType(elements.getTypeElement(impl.canonicalName()), + cacheType.getTypeArguments().get(0), + cacheType.getTypeArguments().get(1))); + } + + private static String getCacheTypeConfigPath(TypeElement cacheContract) { + return cacheContract.getAnnotationMirrors().stream() + .filter(a -> ANNOTATION_CACHE.canonicalName().equals(a.getAnnotationType().asElement().toString())) + .flatMap(a -> a.getElementValues().entrySet().stream()) + .filter(e -> e.getKey().getSimpleName().contentEquals("value")) + .map(e -> e.getValue().getValue().toString()) + .findFirst() + .orElseThrow(() -> new IllegalStateException("@Cache annotation config path not found!")); + } + + private MethodSpec getCacheMethodConfig(TypeElement cacheContract, DeclaredType cacheType) { + final String configPath = getCacheTypeConfigPath(cacheContract); + + final ClassName cacheContractName = ClassName.get(cacheContract); + final String methodName = "%sConfig".formatted(cacheContractName.simpleName()); + final DeclaredType extractorType; + final TypeMirror returnType; + if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(CAFFEINE_CACHE.canonicalName())) { + returnType = elements.getTypeElement(CAFFEINE_CACHE_CONFIG.canonicalName()).asType(); + extractorType = types.getDeclaredType(elements.getTypeElement(CLASS_CONFIG_EXTRACTOR.canonicalName()), returnType); + } else if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(REDIS_CACHE.canonicalName())) { + returnType = elements.getTypeElement(REDIS_CACHE_CONFIG.canonicalName()).asType(); + extractorType = types.getDeclaredType(elements.getTypeElement(CLASS_CONFIG_EXTRACTOR.canonicalName()), returnType); + } else { + throw new UnsupportedOperationException("Unknown implementation: " + cacheContract.getQualifiedName()); + } + + return MethodSpec.methodBuilder(methodName) + .addAnnotation(AnnotationSpec.builder(Tag.class) + .addMember("value", cacheContractName.simpleName() + ".class") + .build()) + .addModifiers(Modifier.DEFAULT, Modifier.PUBLIC) + .addParameter(CLASS_CONFIG, "config") + .addParameter(TypeName.get(extractorType), "extractor") + .addStatement("return extractor.extract(config.get($S))", configPath) + .returns(TypeName.get(returnType)) + .build(); + } + + private static ClassName getCacheImpl(TypeElement cacheContract) { + final ClassName cacheImplName = ClassName.get(cacheContract); + return ClassName.get(cacheImplName.packageName(), "$%sImpl".formatted(cacheImplName.simpleName())); + } + + private MethodSpec getCacheMethodImpl(TypeElement cacheContract, DeclaredType cacheType) { + final ClassName cacheImplName = getCacheImpl(cacheContract); + final String methodName = "%sImpl".formatted(cacheImplName.simpleName()); + if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(CAFFEINE_CACHE.canonicalName())) { + return MethodSpec.methodBuilder(methodName) + .addModifiers(Modifier.DEFAULT, Modifier.PUBLIC) + .addParameter(ParameterSpec.builder(CAFFEINE_CACHE_CONFIG, "config") + .addAnnotation(AnnotationSpec.builder(Tag.class) + .addMember("value", cacheContract.getSimpleName().toString() + ".class") + .build()) + .build()) + .addParameter(CAFFEINE_CACHE_FACTORY, "factory") + .addParameter(CAFFEINE_TELEMETRY, "telemetry") + .addStatement("return new $L(config, factory, telemetry)", cacheImplName) + .returns(TypeName.get(cacheContract.asType())) + .build(); + } else if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(REDIS_CACHE.canonicalName())) { + final TypeMirror keyType = cacheType.getTypeArguments().get(0); + final TypeMirror valueType = cacheType.getTypeArguments().get(1); + final DeclaredType keyMapperType = types.getDeclaredType(elements.getTypeElement(REDIS_CACHE_MAPPER_KEY.canonicalName()), keyType); + final DeclaredType valueMapperType = types.getDeclaredType(elements.getTypeElement(REDIS_CACHE_MAPPER_VALUE.canonicalName()), valueType); + return MethodSpec.methodBuilder(methodName) + .addModifiers(Modifier.DEFAULT, Modifier.PUBLIC) + .addParameter(ParameterSpec.builder(REDIS_CACHE_CONFIG, "config") + .addAnnotation(AnnotationSpec.builder(Tag.class) + .addMember("value", cacheContract.getSimpleName().toString() + ".class") + .build()) + .build()) + .addParameter(REDIS_CACHE_CLIENT_SYNC, "syncClient") + .addParameter(REDIS_CACHE_CLIENT_REACTIVE, "reactiveClient") + .addParameter(REDIS_TELEMETRY, "telemetry") + .addParameter(TypeName.get(keyMapperType), "keyMapper") + .addParameter(TypeName.get(valueMapperType), "valueMapper") + .addStatement("return new $L(config, syncClient, reactiveClient, telemetry, keyMapper, valueMapper)", methodName) + .returns(TypeName.get(cacheContract.asType())) + .build(); + } else { + throw new UnsupportedOperationException("Unknown implementation: " + cacheContract.getQualifiedName()); + } + } + + private MethodSpec getCacheConstructor(TypeElement cacheContract, DeclaredType cacheType) { + final String configPath = getCacheTypeConfigPath(cacheContract); + if (!NAME_PATTERN.matcher(configPath).find()) { + throw new IllegalArgumentException("Cache config path doesn't match pattern: " + NAME_PATTERN); + } + + if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(CAFFEINE_CACHE.canonicalName())) { + return MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(CAFFEINE_CACHE_CONFIG, "config") + .addParameter(CAFFEINE_CACHE_FACTORY, "factory") + .addParameter(CAFFEINE_TELEMETRY, "telemetry") + .addStatement("super($S, config, factory, telemetry)", configPath) + .build(); + } else if (((TypeElement) cacheType.asElement()).getQualifiedName().contentEquals(REDIS_CACHE.canonicalName())) { + final TypeMirror keyType = ((DeclaredType) cacheContract.asType()).getTypeArguments().get(0); + final TypeMirror valueType = ((DeclaredType) cacheContract.asType()).getTypeArguments().get(1); + final DeclaredType keyMapperType = types.getDeclaredType(elements.getTypeElement(REDIS_CACHE_MAPPER_KEY.canonicalName()), keyType); + final DeclaredType valueMapperType = types.getDeclaredType(elements.getTypeElement(REDIS_CACHE_MAPPER_VALUE.canonicalName()), valueType); + return MethodSpec.constructorBuilder() + .addModifiers(Modifier.PROTECTED) + .addParameter(REDIS_CACHE_CONFIG, "config") + .addParameter(REDIS_CACHE_CLIENT_SYNC, "syncClient") + .addParameter(REDIS_CACHE_CLIENT_REACTIVE, "reactiveClient") + .addParameter(REDIS_TELEMETRY, "telemetry") + .addParameter(TypeName.get(keyMapperType), "keyMapper") + .addParameter(TypeName.get(valueMapperType), "valueMapper") + .addStatement("super($S, config, syncClient, reactiveClient, telemetry, keyMapper, valueMapper)", configPath) + .build(); + } else { + throw new UnsupportedOperationException("Unknown implementation: " + cacheContract.getQualifiedName()); + } + } + + private List getAnnotatedElements(RoundEnvironment roundEnv) { + return getSupportedAnnotations().stream() + .flatMap(a -> { + var annotationType = elements.getTypeElement(a); + return roundEnv.getElementsAnnotatedWith(annotationType).stream(); + }) + .filter(a -> a instanceof TypeElement) + .map(a -> ((TypeElement) a)) + .toList(); + } + + private String getPackage(Element element) { + return processingEnv.getElementUtils().getPackageOf(element).getQualifiedName().toString(); + } +} diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessor.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessor.java deleted file mode 100644 index 8f608f267..000000000 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessor.java +++ /dev/null @@ -1,180 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor; - -import ru.tinkoff.kora.annotation.processor.common.AbstractKoraProcessor; -import ru.tinkoff.kora.annotation.processor.common.CommonUtils; -import ru.tinkoff.kora.annotation.processor.common.MethodUtils; -import ru.tinkoff.kora.cache.annotation.*; -import ru.tinkoff.kora.common.annotation.Generated; - -import javax.annotation.processing.Filer; -import javax.annotation.processing.ProcessingEnvironment; -import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; -import javax.tools.Diagnostic; -import javax.tools.JavaFileObject; -import java.io.IOException; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import static ru.tinkoff.kora.cache.annotation.processor.CacheOperationManager.getCacheOperation; - -public class CacheKeyAnnotationProcessor extends AbstractKoraProcessor { - - private static final Set CACHE_KEY_GENERATED = new HashSet<>(); - - private static Set> getSupportedAnnotations() { - return Set.of(Cacheable.class, Cacheables.class, - CachePut.class, CachePuts.class, - CacheInvalidate.class, CacheInvalidates.class); - } - - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - CACHE_KEY_GENERATED.clear(); - CacheOperationManager.reset(); - } - - @Override - public Set getSupportedAnnotationTypes() { - return getSupportedAnnotations().stream() - .map(Class::getCanonicalName) - .collect(Collectors.toSet()); - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - final List annotatedElements = getAnnotatedElements(roundEnv); - for (ExecutableElement method : annotatedElements) { - try { - final String packageName = getPackage(method); - final CacheOperation operation = getCacheOperation(method, processingEnv); - - final List cacheAnnotations = getSupportedAnnotations().stream() - .map(method::getAnnotation) - .filter(Objects::nonNull) - .toList(); - - final List annotationNames = cacheAnnotations.stream().map(a -> a.annotationType().getName()).toList(); - if (cacheAnnotations.size() > 1) { - throw new IllegalStateException("Multiple annotations found " + annotationNames - + ", when expected only one of them for " + operation.meta().origin()); - } - - if (operation.meta().type().equals(CacheMeta.Type.GET) || operation.meta().type().equals(CacheMeta.Type.PUT)) { - if (MethodUtils.isVoid(method)) { - throw new IllegalStateException(annotationNames + " annotation can't return Void type, but was for " + operation.meta().origin()); - } - - if (MethodUtils.isMono(method)) { - final DeclaredType returnType = (DeclaredType) method.getReturnType(); - if (returnType.getTypeArguments().stream().anyMatch(CommonUtils::isVoid)) { - throw new IllegalStateException(annotationNames + " annotation can't return " + returnType + " with Void type erasure , but was for " + operation.meta().origin()); - } - } else if (MethodUtils.isFuture(method)) { - throw new IllegalArgumentException(annotationNames + " annotation doesn't support return type " + method.getReturnType() + " in " + operation.meta().origin()); - } else if (MethodUtils.isFlux(method)) { - throw new IllegalArgumentException(annotationNames + " annotation doesn't support return type " + method.getReturnType() + " in " + operation.meta().origin()); - } - } - - if (CACHE_KEY_GENERATED.contains(operation.key().canonicalName())) { - continue; - } - - final String methodParameters = String.join(", ", operation.meta().getParametersNames(method)); - final List parameters = operation.meta().getParameters(method); - final String recordArguments = parameters.stream() - .map(p -> p.asType().toString() + " " + p.getSimpleName().toString()) - .collect(Collectors.joining(", ")); - - final String toStringParameters; - if (parameters.stream().anyMatch(p -> p.asType().toString().equals(String.class.getCanonicalName()))) { - toStringParameters = parameters.stream() - .map(a -> a.getSimpleName().toString()) - .collect(Collectors.joining(" + \"-\" + ")); - } else { - toStringParameters = parameters.stream() - .map(a -> "String.valueOf(" + a.getSimpleName().toString() + ")") - .collect(Collectors.joining(" + \"-\" + ")); - } - - final String template = """ - package %s; - - import java.util.List; - import java.util.Arrays; - import java.lang.Object; - import ru.tinkoff.kora.cache.CacheKey; - import ru.tinkoff.kora.common.annotation.Generated; - - @%s("%s") - public record %s(%s) implements CacheKey { - - @Override - public List values() { - return Arrays.asList(%s); - } - - @Override - public String toString() { - return %s; - } - }"""; - - final String record = String.format(template, packageName, Generated.class.getSimpleName(), this.getClass().getCanonicalName(), - operation.key().simpleName(), recordArguments, methodParameters, toStringParameters); - writeTo(packageName, operation.key().simpleName(), record, processingEnv.getFiler()); - CACHE_KEY_GENERATED.add(operation.key().canonicalName()); - } catch (IOException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), method); - e.printStackTrace(); - return false; - } catch (IllegalArgumentException | IllegalStateException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), method); - return false; - } - } - - return false; - } - - public void writeTo(String packageName, String recordName, String recordClass, Filer filer) throws IOException { - final String fileName = packageName.isEmpty() - ? recordName - : packageName + "." + recordName; - - final JavaFileObject filerSourceFile = filer.createSourceFile(fileName, new Element[1]); - try (Writer writer = filerSourceFile.openWriter()) { - writer.write(recordClass); - } catch (Exception e) { - try { - filerSourceFile.delete(); - } catch (Exception ignored) { - // do nothing - } - throw e; - } - } - - private static List getAnnotatedElements(RoundEnvironment roundEnv) { - return getSupportedAnnotations().stream() - .flatMap(a -> roundEnv.getElementsAnnotatedWith(a).stream()) - .filter(a -> a instanceof ExecutableElement) - .map(a -> ((ExecutableElement) a)) - .toList(); - } - - private String getPackage(Element element) { - return processingEnv.getElementUtils().getPackageOf(element).getQualifiedName().toString(); - } -} diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMeta.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMeta.java deleted file mode 100644 index db86b3f14..000000000 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMeta.java +++ /dev/null @@ -1,62 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.VariableElement; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -public record CacheMeta(Type type, List managers, List parameters, Origin origin) { - - public record Manager(String name, List tags) {} - - public record Origin(String className, String methodName) { - - @Override - public String toString() { - return "[class=" + className + ", method=" + methodName + ']'; - } - } - - public enum Type { - GET, - PUT, - EVICT, - EVICT_ALL - } - - public List getParametersNames(ExecutableElement method) { - return getParameters(method).stream() - .map(p -> p.getSimpleName().toString()) - .toList(); - } - - public List getParameters(ExecutableElement method) { - if (parameters.isEmpty()) { - return method.getParameters().stream() - .filter(this::isParameterSupported) - .map(p -> ((VariableElement) p)) - .toList(); - - } else { - final List methodParameters = new ArrayList<>(); - for (String parameter : parameters) { - final Optional arg = method.getParameters().stream() - .filter(p -> p.getSimpleName().contentEquals(parameter)) - .findFirst(); - - if (arg.isPresent()) { - methodParameters.add(arg.get()); - } else { - throw new IllegalArgumentException("Specified CacheKey parameter '" + parameter + "' is not present in method signature: " + origin()); - } - } - - return methodParameters; - } - } - - public boolean isParameterSupported(VariableElement parameter) { - return parameters.isEmpty() || parameters.contains(parameter.toString()); - } -} diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMetaUtils.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMetaUtils.java deleted file mode 100644 index b57c754ec..000000000 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheMetaUtils.java +++ /dev/null @@ -1,194 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor; - -import ru.tinkoff.kora.annotation.processor.common.ProcessingError; -import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; -import ru.tinkoff.kora.cache.annotation.*; -import ru.tinkoff.kora.cache.annotation.processor.CacheMeta.Manager; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.tools.Diagnostic; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.stream.Stream; - -final class CacheMetaUtils { - - private static final Set> CACHE_ANNOTATIONS = Set.of(Cacheable.class, Cacheables.class, - CachePut.class, CachePuts.class, - CacheInvalidate.class, CacheInvalidates.class); - - private CacheMetaUtils() {} - - static List> getTags(ExecutableElement element) { - final List repetableNames = Stream.of(Cacheables.class, CachePuts.class, CacheInvalidates.class) - .map(Class::getCanonicalName) - .toList(); - - return element.getAnnotationMirrors().stream() - .filter(a -> CACHE_ANNOTATIONS.stream().anyMatch(type -> a.getAnnotationType().toString().contentEquals(type.getCanonicalName()))) - .flatMap(a -> { - if (repetableNames.contains(a.getAnnotationType().toString())) { - return a.getElementValues().entrySet().stream() - .filter(e -> e.getKey().getSimpleName().contentEquals("value")) - .flatMap(e -> { - final Object value = e.getValue().getValue(); - if (value instanceof List) { - return ((List) value).stream() - .map(inner -> getInnerAnnotationTags(((AnnotationMirror) inner))); - } else { - return Stream.of(List.of(value.toString())); - } - }); - } else { - return Stream.of(getInnerAnnotationTags(a)); - } - } - ).toList(); - } - - private static List getInnerAnnotationTags(AnnotationMirror annotation) { - return annotation.getElementValues().entrySet().stream() - .filter(e -> e.getKey().getSimpleName().contentEquals("tags")) - .flatMap(e -> { - final Object value = e.getValue().getValue(); - return (value instanceof List) - ? ((List) value).stream().map(Object::toString) - : Stream.of(value.toString()); - }) - .toList(); - } - - static CacheMeta getCacheMeta(ExecutableElement method) { - final List cacheables = getCacheableAnnotations(method); - final List puts = getCachePutAnnotations(method); - final List invalidates = getCacheInvalidateAnnotations(method); - - final String className = method.getEnclosingElement().getSimpleName().toString(); - final String methodName = method.getSimpleName().toString(); - final CacheMeta.Origin origin = new CacheMeta.Origin(className, methodName); - final List> allTags = getTags(method); - - if (!cacheables.isEmpty()) { - final List managers = new ArrayList<>(); - final List> managerParameters = new ArrayList<>(); - for (int i = 0; i < cacheables.size(); i++) { - final Cacheable annotation = cacheables.get(i); - final List tags = allTags.get(i); - final Manager manager = new Manager(annotation.name(), tags); - managers.add(manager); - - final List annotationParameters = List.of(annotation.parameters()); - for (List managerParameter : managerParameters) { - if (!managerParameter.equals(annotationParameters)) { - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, - annotation.getClass() + " parameters mismatch for different annotations for " + origin, method)); - } - } - - managerParameters.add(annotationParameters); - } - - return new CacheMeta(CacheMeta.Type.GET, managers, managerParameters.get(0), origin); - } else if (!puts.isEmpty()) { - final List managers = new ArrayList<>(); - final List> managerParameters = new ArrayList<>(); - for (int i = 0; i < puts.size(); i++) { - final CachePut annotation = puts.get(i); - final List tags = allTags.get(i); - final Manager manager = new Manager(annotation.name(), tags); - managers.add(manager); - - final List annotationParameters = List.of(annotation.parameters()); - for (List managerParameter : managerParameters) { - if (!managerParameter.equals(annotationParameters)) { - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, - annotation.getClass() + " parameters mismatch for different annotations for " + origin, method)); - } - } - - managerParameters.add(annotationParameters); - } - - return new CacheMeta(CacheMeta.Type.PUT, managers, managerParameters.get(0), origin); - } else if (!invalidates.isEmpty()) { - final List managers = new ArrayList<>(); - final List> managerParameters = new ArrayList<>(); - final boolean anyInvalidateAll = invalidates.stream().anyMatch(CacheInvalidate::invalidateAll); - final boolean allInvalidateAll = invalidates.stream().allMatch(CacheInvalidate::invalidateAll); - - if (anyInvalidateAll && !allInvalidateAll) { - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, - CacheInvalidate.class + " not all annotations are marked 'invalidateAll' out of all for " + origin, method)); - } - - for (int i = 0; i < invalidates.size(); i++) { - final CacheInvalidate annotation = invalidates.get(i); - final List tags = allTags.get(i); - final Manager manager = new Manager(annotation.name(), tags); - managers.add(manager); - - final List annotationParameters = List.of(annotation.parameters()); - for (List managerParameter : managerParameters) { - if (!managerParameter.equals(annotationParameters)) { - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, - annotation.getClass() + " parameters mismatch for different annotations for " + origin, method)); - } - } - - managerParameters.add(annotationParameters); - } - - final CacheMeta.Type type = (allInvalidateAll) ? CacheMeta.Type.EVICT_ALL : CacheMeta.Type.EVICT; - return new CacheMeta(type, managers, managerParameters.get(0), origin); - } - - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, - "None of " + CACHE_ANNOTATIONS + " cache annotations found", method)); - } - - private static List getCacheableAnnotations(ExecutableElement method) { - final Cacheables annotations = method.getAnnotation(Cacheables.class); - if (annotations != null) { - return Arrays.stream(annotations.value()).toList(); - } - - final Cacheable annotation = method.getAnnotation(Cacheable.class); - if (annotation != null) { - return List.of(annotation); - } - - return List.of(); - } - - private static List getCachePutAnnotations(ExecutableElement method) { - final CachePuts annotations = method.getAnnotation(CachePuts.class); - if (annotations != null) { - return Arrays.stream(annotations.value()).toList(); - } - - final CachePut annotation = method.getAnnotation(CachePut.class); - if (annotation != null) { - return List.of(annotation); - } - - return List.of(); - } - - private static List getCacheInvalidateAnnotations(ExecutableElement method) { - final CacheInvalidates annotations = method.getAnnotation(CacheInvalidates.class); - if (annotations != null) { - return Arrays.stream(annotations.value()).toList(); - } - - final CacheInvalidate annotation = method.getAnnotation(CacheInvalidate.class); - if (annotation != null) { - return List.of(annotation); - } - - return List.of(); - } -} diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperation.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperation.java index 0047ebff8..32273787f 100644 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperation.java +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperation.java @@ -1,18 +1,59 @@ package ru.tinkoff.kora.cache.annotation.processor; -import javax.annotation.Nullable; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; -public record CacheOperation(CacheMeta meta, Key key, Value value) { +public record CacheOperation(Type type, List cacheImplementations, List parameters, Origin origin) { - public record Value(@Nullable String canonicalName) {} + public record Origin(String className, String methodName) { - public record Key(String packageName, String simpleName) { + @Override + public String toString() { + return "[class=" + className + ", method=" + methodName + ']'; + } + } - public String canonicalName() { - return (packageName.isEmpty()) - ? simpleName - : packageName + "." + simpleName; + public enum Type { + GET, + PUT, + EVICT, + EVICT_ALL + } + + public List getParametersNames(ExecutableElement method) { + return getParameters(method).stream() + .map(p -> p.getSimpleName().toString()) + .toList(); + } + + public List getParameters(ExecutableElement method) { + if (parameters.isEmpty()) { + return method.getParameters().stream() + .filter(this::isParameterSupported) + .map(p -> ((VariableElement) p)) + .toList(); + } else { + final List methodParameters = new ArrayList<>(); + for (var parameter : parameters) { + final Optional arg = method.getParameters().stream() + .filter(p -> p.getSimpleName().contentEquals(parameter.getSimpleName())) + .findFirst(); + + if (arg.isPresent()) { + methodParameters.add(arg.get()); + } else { + throw new IllegalArgumentException("Specified CacheKey parameter '" + parameter + "' is not present in method signature: " + origin()); + } + } + + return methodParameters; } } + public boolean isParameterSupported(VariableElement parameter) { + return parameters.isEmpty() || parameters.stream().anyMatch(p -> p.getSimpleName().contentEquals(parameter.toString())); + } } diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationManager.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationManager.java deleted file mode 100644 index 122fe2ecf..000000000 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationManager.java +++ /dev/null @@ -1,109 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor; - -import ru.tinkoff.kora.annotation.processor.common.CommonUtils; -import ru.tinkoff.kora.annotation.processor.common.MethodUtils; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.PackageElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import java.util.*; -import java.util.regex.Pattern; - -public final class CacheOperationManager { - - private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z][0-9a-zA-Z_]*"); - private static final Map CACHE_NAME_TO_CACHE_KEY = new HashMap<>(); - - private record CacheSignature(CacheSignature.Type key, String value, List parameterTypes, CacheMeta.Origin origin) { - - public record Type(String packageName, String simpleName) {} - } - - static void reset() { - CACHE_NAME_TO_CACHE_KEY.clear(); - } - - public static CacheOperation getCacheOperation(ExecutableElement method, ProcessingEnvironment env) { - final CacheMeta meta = CacheMetaUtils.getCacheMeta(method); - final CacheSignature signature = getCacheSignature(meta, method, env); - - return new CacheOperation(meta, - new CacheOperation.Key(signature.key().packageName(), signature.key().simpleName()), - new CacheOperation.Value(signature.value()) - ); - } - - private static CacheSignature getCacheSignature(CacheMeta meta, ExecutableElement method, ProcessingEnvironment env) { - for (CacheMeta.Manager manager : meta.managers()) { - if (!NAME_PATTERN.matcher(manager.name()).matches()) { - throw new IllegalArgumentException("Cache name for " + meta.origin() + " doesn't match pattern: " + NAME_PATTERN); - } - } - - final List parameterTypes = new ArrayList<>(); - if (meta.parameters().isEmpty()) { - method.getParameters().forEach(p -> parameterTypes.add(p.asType().toString())); - } else { - for (String parameter : meta.parameters()) { - method.getParameters().stream() - .filter(p -> p.getSimpleName().contentEquals(parameter)) - .findFirst() - .ifPresent(p -> parameterTypes.add(p.asType().toString())); - } - } - - final TypeMirror returnType = MethodUtils.isMono(method) - ? ((DeclaredType) method.getReturnType()).getTypeArguments().get(0) - : method.getReturnType(); - - final CacheSignature signature = meta.managers().stream() - .map(CacheMeta.Manager::name) - .map(CACHE_NAME_TO_CACHE_KEY::get) - .filter(Objects::nonNull) - .findFirst() - .orElseGet(() -> { - final String cacheName = meta.managers().get(0).name(); - final PackageElement packageElement = (PackageElement) method.getEnclosingElement().getEnclosingElement(); - final String packageName = (packageElement.isUnnamed()) - ? "" - : packageElement.getQualifiedName().toString(); - - final String nonVoidReturnType = CommonUtils.isVoid(returnType) - ? null - : returnType.toString(); - - final CacheSignature.Type key = new CacheSignature.Type(packageName, "$CacheKey__" + cacheName); - final CacheSignature cacheSignature = new CacheSignature(key, nonVoidReturnType, parameterTypes, meta.origin()); - for (CacheMeta.Manager manager : meta.managers()) { - CACHE_NAME_TO_CACHE_KEY.put(manager.name(), cacheSignature); - } - - return cacheSignature; - }); - - if (!meta.type().equals(CacheMeta.Type.EVICT_ALL) && !meta.type().equals(CacheMeta.Type.EVICT)) { - if (!signature.parameterTypes().equals(parameterTypes)) { - throw new IllegalStateException("Cache Key parameters from " + signature.origin() + " mismatch with " + meta.origin() - + ", expected " + signature.parameterTypes() + " but was " + parameterTypes); - } - - // Replace evict (void) operations previously saved - if (!CommonUtils.isVoid(returnType)) { - final String returnAsStr = returnType.toString(); - if (signature.value != null && !returnAsStr.equals(signature.value)) { - throw new IllegalStateException("Cache Value type from " + signature.origin() + " mismatch with " + meta.origin() - + ", expected " + signature.value() + " but was " + returnType); - } else if (signature.value == null) { - final CacheSignature nonEvictSignature = new CacheSignature(signature.key(), returnAsStr, signature.parameterTypes(), meta.origin()); - for (CacheMeta.Manager manager : meta.managers()) { - CACHE_NAME_TO_CACHE_KEY.put(manager.name(), nonEvictSignature); - } - } - } - } - - return signature; - } -} diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationUtils.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationUtils.java new file mode 100644 index 000000000..b04c22a76 --- /dev/null +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/CacheOperationUtils.java @@ -0,0 +1,158 @@ +package ru.tinkoff.kora.cache.annotation.processor; + +import com.squareup.javapoet.ClassName; +import ru.tinkoff.kora.annotation.processor.common.ProcessingError; +import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; + +import javax.lang.model.element.*; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public final class CacheOperationUtils { + + private static final ClassName ANNOTATION_CACHEABLE = ClassName.get("ru.tinkoff.kora.cache.annotation", "Cacheable"); + private static final ClassName ANNOTATION_CACHEABLES = ClassName.get("ru.tinkoff.kora.cache.annotation", "Cacheables"); + private static final ClassName ANNOTATION_CACHE_PUT = ClassName.get("ru.tinkoff.kora.cache.annotation", "CachePut"); + private static final ClassName ANNOTATION_CACHE_PUTS = ClassName.get("ru.tinkoff.kora.cache.annotation", "CachePuts"); + private static final ClassName ANNOTATION_CACHE_INVALIDATE = ClassName.get("ru.tinkoff.kora.cache.annotation", "CacheInvalidate"); + private static final ClassName ANNOTATION_CACHE_INVALIDATES = ClassName.get("ru.tinkoff.kora.cache.annotation", "CacheInvalidates"); + + private static final Set CACHE_ANNOTATIONS = Set.of( + ANNOTATION_CACHEABLE.canonicalName(), ANNOTATION_CACHEABLES.canonicalName(), + ANNOTATION_CACHE_PUT.canonicalName(), ANNOTATION_CACHE_PUTS.canonicalName(), + ANNOTATION_CACHE_INVALIDATE.canonicalName(), ANNOTATION_CACHE_INVALIDATES.canonicalName() + ); + + private CacheOperationUtils() {} + + public static CacheOperation getCacheMeta(ExecutableElement method) { + final List cacheables = getRepeatedAnnotations(method, ANNOTATION_CACHEABLE.canonicalName(), ANNOTATION_CACHEABLES.canonicalName()); + final List puts = getRepeatedAnnotations(method, ANNOTATION_CACHE_PUT.canonicalName(), ANNOTATION_CACHE_PUTS.canonicalName()); + final List invalidates = getRepeatedAnnotations(method, ANNOTATION_CACHE_INVALIDATE.canonicalName(), ANNOTATION_CACHE_INVALIDATES.canonicalName()); + + final String className = method.getEnclosingElement().getSimpleName().toString(); + final String methodName = method.getSimpleName().toString(); + final CacheOperation.Origin origin = new CacheOperation.Origin(className, methodName); + + if (!cacheables.isEmpty()) { + if (!puts.isEmpty() || !invalidates.isEmpty()) { + throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, + "Method must have Cache annotations with same operation type, but got multiple different operation types for " + origin, method)); + } + + return getOperation(method, cacheables, CacheOperation.Type.GET); + } else if (!puts.isEmpty()) { + if (!invalidates.isEmpty()) { + throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, + "Method must have Cache annotations with same operation type, but got multiple different operation types for " + origin, method)); + } + + return getOperation(method, puts, CacheOperation.Type.PUT); + } else if (!invalidates.isEmpty()) { + var invalidateAlls = invalidates.stream() + .flatMap(a -> a.getElementValues().entrySet().stream()) + .filter(e -> e.getKey().getSimpleName().contentEquals("invalidateAll")) + .map(e -> ((boolean) e.getValue().getValue())) + .toList(); + + final boolean anyInvalidateAll = !invalidateAlls.isEmpty() && invalidateAlls.stream().anyMatch(v -> v); + final boolean allInvalidateAll = !invalidateAlls.isEmpty() && invalidateAlls.stream().allMatch(v -> v); + + if (anyInvalidateAll && !allInvalidateAll) { + throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, + ANNOTATION_CACHE_INVALIDATE.canonicalName() + " not all annotations are marked 'invalidateAll' out of all for " + origin, method)); + } + + final CacheOperation.Type type = (allInvalidateAll) ? CacheOperation.Type.EVICT_ALL : CacheOperation.Type.EVICT; + return getOperation(method, invalidates, type); + } + + throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, + "None of " + CACHE_ANNOTATIONS + " cache annotations found", method)); + } + + private static CacheOperation getOperation(ExecutableElement method, List cacheAnnotations, CacheOperation.Type type) { + final String className = method.getEnclosingElement().getSimpleName().toString(); + final String methodName = method.getSimpleName().toString(); + final CacheOperation.Origin origin = new CacheOperation.Origin(className, methodName); + + final List> cacheKeyArguments = new ArrayList<>(); + final List cacheImpls = new ArrayList<>(); + for (var annotation : cacheAnnotations) { + var parameters = annotation.getElementValues().entrySet().stream() + .filter(e -> e.getKey().getSimpleName().contentEquals("parameters")) + .map(e -> ((List) (e.getValue()).getValue()).stream() + .filter(a -> a instanceof AnnotationValue) + .map(a -> ((AnnotationValue) a).getValue().toString()) + .toList()) + .findFirst() + .orElse(Collections.emptyList()); + + if (parameters.isEmpty()) { + parameters = method.getParameters().stream() + .map(p -> p.getSimpleName().toString()) + .toList(); + } else { + for (String parameter : parameters) { + if (method.getParameters().stream().noneMatch(p -> p.getSimpleName().contentEquals(parameter))) { + throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, + "Unknown method parameter is declared: " + parameter, method)); + } + } + } + + for (List arguments : cacheKeyArguments) { + if (!arguments.equals(parameters)) { + throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.ERROR, + annotation.getClass() + " parameters mismatch for different annotations for: " + origin, method)); + } + } + + cacheKeyArguments.add(parameters); + + final String cacheImpl = annotation.getElementValues().entrySet().stream() + .filter(e -> e.getKey().getSimpleName().contentEquals("value")) + .map(e -> String.valueOf(e.getValue().getValue())) + .findFirst() + .orElseThrow(); + cacheImpls.add(cacheImpl); + } + + final List parameterResult = cacheKeyArguments.get(0).stream() + .flatMap(param -> method.getParameters().stream().filter(p -> p.getSimpleName().contentEquals(param))) + .map(p -> ((VariableElement) p)) + .toList(); + + return new CacheOperation(type, cacheImpls, parameterResult, origin); + } + + private static List getRepeatedAnnotations(Element element, + Class annotation, + Class parentAnnotation) { + return getRepeatedAnnotations(element, annotation.getCanonicalName(), parentAnnotation.getCanonicalName()); + } + + private static List getRepeatedAnnotations(Element element, + String annotation, + String parentAnnotation) { + final List repeated = element.getAnnotationMirrors().stream() + .filter(pa -> pa.getAnnotationType().toString().contentEquals(parentAnnotation)) + .flatMap(pa -> pa.getElementValues().entrySet().stream()) + .flatMap(e -> ((List) e.getValue().getValue()).stream().map(AnnotationMirror.class::cast)) + .filter(a -> a.getAnnotationType().toString().contentEquals(annotation)) + .toList(); + + if (!repeated.isEmpty()) { + return repeated; + } + + return element.getAnnotationMirrors().stream() + .filter(a -> a.getAnnotationType().toString().contentEquals(annotation)) + .map(a -> ((AnnotationMirror) a)) + .toList(); + } +} diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/AbstractAopCacheAspect.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/AbstractAopCacheAspect.java index 54d8f5eda..f831ac3d1 100644 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/AbstractAopCacheAspect.java +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/AbstractAopCacheAspect.java @@ -1,77 +1,30 @@ package ru.tinkoff.kora.cache.annotation.processor.aop; -import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; -import ru.tinkoff.kora.annotation.processor.common.ProcessingError; -import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; import ru.tinkoff.kora.aop.annotation.processor.KoraAspect; -import ru.tinkoff.kora.cache.Cache; -import ru.tinkoff.kora.cache.CacheManager; -import ru.tinkoff.kora.cache.annotation.processor.CacheMeta; import ru.tinkoff.kora.cache.annotation.processor.CacheOperation; -import ru.tinkoff.kora.common.Tag; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import static com.squareup.javapoet.CodeBlock.joining; abstract class AbstractAopCacheAspect implements KoraAspect { - record CacheMirrors(TypeMirror manager, TypeMirror cache) {} + private static final ClassName KEY_CACHE = ClassName.get("ru.tinkoff.kora.cache", "CacheKey"); - CacheMirrors getCacheMirrors(CacheOperation operation, ExecutableElement method, ProcessingEnvironment env) { - final TypeElement keyElement = env.getElementUtils().getTypeElement(operation.key().canonicalName()); - if (keyElement == null) { - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.WARNING, "Cache Key is not yet generated, will try next round...", method)); - } - - final TypeElement valueElement = env.getElementUtils().getTypeElement(operation.value().canonicalName()); - if (valueElement == null) { - throw new ProcessingErrorException(new ProcessingError(Diagnostic.Kind.WARNING, "Cache Return type is not yet known, will try next round...", method)); - } - - final TypeMirror managerType = env.getTypeUtils().getDeclaredType(env.getElementUtils().getTypeElement(CacheManager.class.getCanonicalName()), - keyElement.asType(), - valueElement.asType()); - - final TypeMirror cacheType = env.getTypeUtils().getDeclaredType(env.getElementUtils().getTypeElement(Cache.class.getCanonicalName()), - keyElement.asType(), - valueElement.asType()); - - return new CacheMirrors(managerType, cacheType); + ClassName getCacheKey(CacheOperation operation) { + return KEY_CACHE; } - List getCacheFields(CacheOperation operation, CacheMirrors mirror, AspectContext aspectContext) { + List getCacheFields(CacheOperation operation, ProcessingEnvironment env, AspectContext aspectContext) { final List cacheFields = new ArrayList<>(); - for (CacheMeta.Manager manager : operation.meta().managers()) { - final List managerTags; - if (manager.tags().isEmpty()) { - managerTags = List.of(); - } else if (manager.tags().size() == 1) { - managerTags = List.of(AnnotationSpec.builder(Tag.class) - .addMember("value", manager.tags().get(0)) - .build()); - } else { - final String tagValue = manager.tags().stream() - .collect(Collectors.joining(", ", "{", "}")); - - managerTags = List.of(AnnotationSpec.builder(Tag.class) - .addMember("value", tagValue) - .build()); - } - - var fieldManager = aspectContext.fieldFactory().constructorParam(mirror.manager(), managerTags); - var fieldCache = aspectContext.fieldFactory().constructorInitialized(mirror.cache(), CodeBlock.builder() - .add("$L.getCache(\"$L\")", fieldManager, manager.name()) - .build()); - + for (var cacheImpl : operation.cacheImplementations()) { + var cacheElement = env.getElementUtils().getTypeElement(cacheImpl); + var fieldCache = aspectContext.fieldFactory().constructorParam(cacheElement.asType(), List.of()); cacheFields.add(fieldCache); } @@ -79,7 +32,7 @@ List getCacheFields(CacheOperation operation, CacheMirrors mirror, Aspec } String getKeyRecordParameters(CacheOperation operation, ExecutableElement method) { - return String.join(", ", operation.meta().getParametersNames(method)); + return String.join(", ", operation.getParametersNames(method)); } String getSuperMethod(ExecutableElement method, String superCall) { diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheInvalidateAopKoraAspect.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheInvalidateAopKoraAspect.java index 2b8630066..1f23baaa8 100644 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheInvalidateAopKoraAspect.java +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheInvalidateAopKoraAspect.java @@ -1,21 +1,24 @@ package ru.tinkoff.kora.cache.annotation.processor.aop; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import reactor.core.publisher.Flux; import ru.tinkoff.kora.annotation.processor.common.MethodUtils; -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CacheInvalidates; -import ru.tinkoff.kora.cache.annotation.processor.CacheMeta; +import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; import ru.tinkoff.kora.cache.annotation.processor.CacheOperation; +import ru.tinkoff.kora.cache.annotation.processor.CacheOperationUtils; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import java.util.List; import java.util.Set; - -import static ru.tinkoff.kora.cache.annotation.processor.CacheOperationManager.getCacheOperation; +import java.util.concurrent.Future; public class CacheInvalidateAopKoraAspect extends AbstractAopCacheAspect { + private static final ClassName ANNOTATION_CACHE_INVALIDATE = ClassName.get("ru.tinkoff.kora.cache.annotation", "CacheInvalidate"); + private static final ClassName ANNOTATION_CACHE_INVALIDATES = ClassName.get("ru.tinkoff.kora.cache.annotation", "CacheInvalidates"); + private final ProcessingEnvironment env; public CacheInvalidateAopKoraAspect(ProcessingEnvironment env) { @@ -24,27 +27,32 @@ public CacheInvalidateAopKoraAspect(ProcessingEnvironment env) { @Override public Set getSupportedAnnotationTypes() { - return Set.of(CacheInvalidate.class.getCanonicalName(), CacheInvalidates.class.getCanonicalName()); + return Set.of(ANNOTATION_CACHE_INVALIDATE.canonicalName(), ANNOTATION_CACHE_INVALIDATES.canonicalName()); } @Override public ApplyResult apply(ExecutableElement method, String superCall, AspectContext aspectContext) { - final CacheOperation operation = getCacheOperation(method, env); - final CacheMirrors cacheMirrors = getCacheMirrors(operation, method, env); + if (MethodUtils.isFuture(method)) { + throw new ProcessingErrorException("@CacheInvalidate can't be applied for types assignable from " + Future.class, method); + } else if (MethodUtils.isFlux(method)) { + throw new ProcessingErrorException("@CacheInvalidate can't be applied for types assignable from " + Flux.class, method); + } + + final CacheOperation operation = CacheOperationUtils.getCacheMeta(method); + final List cacheFields = getCacheFields(operation, env, aspectContext); - final List cacheFields = getCacheFields(operation, cacheMirrors, aspectContext); final CodeBlock body; if (MethodUtils.isMono(method)) { - if (operation.meta().type() == CacheMeta.Type.EVICT_ALL) { - body = buildBodyMonoAll(method, operation, superCall, cacheFields); + if (operation.type() == CacheOperation.Type.EVICT_ALL) { + body = buildBodyMonoAll(method, operation, cacheFields, superCall); } else { - body = buildBodyMono(method, operation, superCall, cacheFields); + body = buildBodyMono(method, operation, cacheFields, superCall); } } else { - if (operation.meta().type() == CacheMeta.Type.EVICT_ALL) { - body = buildBodySyncAll(method, operation, superCall, cacheFields); + if (operation.type() == CacheOperation.Type.EVICT_ALL) { + body = buildBodySyncAll(method, operation, cacheFields, superCall); } else { - body = buildBodySync(method, operation, superCall, cacheFields); + body = buildBodySync(method, operation, cacheFields, superCall); } } @@ -53,9 +61,8 @@ public ApplyResult apply(ExecutableElement method, String superCall, AspectConte private CodeBlock buildBodySync(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { - final String recordParameters = getKeyRecordParameters(operation, method); + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables @@ -79,19 +86,30 @@ private CodeBlock buildBodySync(ExecutableElement method, builder.append("return value;"); } - return CodeBlock.builder() - .add(""" - var _key = new $L($L); - """, - operation.key().simpleName(), recordParameters) - .add(builder.toString()) - .build(); + if (operation.parameters().size() == 1) { + return CodeBlock.builder() + .add(""" + var _key = $L; + """, + operation.parameters().get(0)) + .add(builder.toString()) + .build(); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + return CodeBlock.builder() + .add(""" + var _key = $T.of($L); + """, + getCacheKey(operation), recordParameters) + .add(builder.toString()) + .build(); + } } private CodeBlock buildBodySyncAll(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables @@ -122,9 +140,8 @@ private CodeBlock buildBodySyncAll(ExecutableElement method, private CodeBlock buildBodyMono(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { - final String recordParameters = getKeyRecordParameters(operation, method); + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables @@ -149,20 +166,30 @@ private CodeBlock buildBodyMono(ExecutableElement method, builder.append(".doOnSuccess(_result -> ").append(cacheFields.get(0)).append(".invalidate(_key));\n"); } - return CodeBlock.builder() - .add(""" - var _key = new $L($L); - """, - operation.key().simpleName(), recordParameters) - .add(builder.toString()) - .build(); - + if (operation.parameters().size() == 1) { + return CodeBlock.builder() + .add(""" + var _key = $L; + """, + operation.parameters().get(0)) + .add(builder.toString()) + .build(); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + return CodeBlock.builder() + .add(""" + var _key = $T.of($L); + """, + getCacheKey(operation), recordParameters) + .add(builder.toString()) + .build(); + } } private CodeBlock buildBodyMonoAll(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CachePutAopKoraAspect.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CachePutAopKoraAspect.java index f82aca966..3a19dd52b 100644 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CachePutAopKoraAspect.java +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CachePutAopKoraAspect.java @@ -1,21 +1,24 @@ package ru.tinkoff.kora.cache.annotation.processor.aop; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import reactor.core.publisher.Flux; import ru.tinkoff.kora.annotation.processor.common.MethodUtils; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.CachePuts; -import ru.tinkoff.kora.cache.annotation.processor.CacheMeta; +import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; import ru.tinkoff.kora.cache.annotation.processor.CacheOperation; +import ru.tinkoff.kora.cache.annotation.processor.CacheOperationUtils; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import java.util.List; import java.util.Set; - -import static ru.tinkoff.kora.cache.annotation.processor.CacheOperationManager.getCacheOperation; +import java.util.concurrent.Future; public class CachePutAopKoraAspect extends AbstractAopCacheAspect { + private static final ClassName ANNOTATION_CACHE_PUT = ClassName.get("ru.tinkoff.kora.cache.annotation", "CachePut"); + private static final ClassName ANNOTATION_CACHE_PUTS = ClassName.get("ru.tinkoff.kora.cache.annotation", "CachePuts"); + private final ProcessingEnvironment env; public CachePutAopKoraAspect(ProcessingEnvironment env) { @@ -24,66 +27,87 @@ public CachePutAopKoraAspect(ProcessingEnvironment env) { @Override public Set getSupportedAnnotationTypes() { - return Set.of(CachePut.class.getCanonicalName(), CachePuts.class.getCanonicalName()); + return Set.of(ANNOTATION_CACHE_PUT.canonicalName(), ANNOTATION_CACHE_PUTS.canonicalName()); } @Override public ApplyResult apply(ExecutableElement method, String superCall, AspectContext aspectContext) { - final CacheOperation operation = getCacheOperation(method, env); - final AbstractAopCacheAspect.CacheMirrors cacheMirrors = getCacheMirrors(operation, method, env); + if (MethodUtils.isFuture(method)) { + throw new ProcessingErrorException("@CachePut can't be applied for types assignable from " + Future.class, method); + } else if (MethodUtils.isFlux(method)) { + throw new ProcessingErrorException("@CachePut can't be applied for types assignable from " + Flux.class, method); + } + + final CacheOperation operation = CacheOperationUtils.getCacheMeta(method); + final List cacheFields = getCacheFields(operation, env, aspectContext); - final List cacheFields = getCacheFields(operation, cacheMirrors, aspectContext); final CodeBlock body = MethodUtils.isMono(method) - ? buildBodyMono(method, operation, superCall, cacheFields) - : buildBodySync(method, operation, superCall, cacheFields); + ? buildBodyMono(method, operation, cacheFields, superCall) + : buildBodySync(method, operation, cacheFields, superCall); return new ApplyResult.MethodBody(body); } private CodeBlock buildBodySync(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { - final String recordParameters = getKeyRecordParameters(operation, method); + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables - final StringBuilder builder = new StringBuilder(); + final CodeBlock.Builder builder = CodeBlock.builder(); // cache super method - builder.append("var _value = ").append(superMethod).append(";\n"); + builder.add("var _value = ").add(superMethod).add(";\n"); + + if (operation.parameters().size() == 1) { + builder.add(""" + var _key = $L; + """, + operation.parameters().get(0)); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + builder.add(""" + var _key = $T.of($L); + """, + getCacheKey(operation), recordParameters); + } // cache put - for (int i = 0; i < cacheFields.size(); i++) { - final String cache = cacheFields.get(i); - builder.append(cache).append(".put(_key, _value);\n"); + for (var cache : cacheFields) { + builder.add(cache).add(".put(_key, _value);\n"); } - builder.append("return _value;"); + builder.add("return _value;"); - return CodeBlock.builder() - .add(""" - var _key = new $L($L); - """, - operation.key().simpleName(), recordParameters) - .add(builder.toString()) - .build(); + return builder.build(); } private CodeBlock buildBodyMono(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { - final String recordParameters = getKeyRecordParameters(operation, method); + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables - final StringBuilder builder = new StringBuilder(); + final CodeBlock.Builder builder = CodeBlock.builder(); // cache super method - builder.append("return ").append(superMethod); if (cacheFields.size() > 1) { - builder.append(".flatMap(_result -> reactor.core.publisher.Flux.merge(java.util.List.of(\n"); + if (operation.parameters().size() == 1) { + builder.add(""" + var _key = $L; + """, operation.parameters().get(0)); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + builder.add(""" + var _key = $T.of($L); + """, getCacheKey(operation), recordParameters); + } + + builder.add("return ") + .add(superMethod) + .add(".flatMap(_result -> $T.merge($T.of(\n", Flux.class, List.class); // cache put for (int i = 0; i < cacheFields.size(); i++) { @@ -91,19 +115,31 @@ private CodeBlock buildBodyMono(ExecutableElement method, final String suffix = (i == cacheFields.size() - 1) ? ".putAsync(_key, _result)\n" : ".putAsync(_key, _result),\n"; - builder.append("\t").append(cache).append(suffix); + builder.add("\t").add(cache).add(suffix); } - builder.append(")).then(Mono.just(_result)));"); + builder.add(")).then(Mono.just(_result)));"); } else { - builder.append(".doOnSuccess(_result -> ").append(cacheFields.get(0)).append(".put(_key, _result));\n"); + builder.add("return ").add(superMethod); + if (operation.parameters().size() == 1) { + builder.add(""" + .doOnSuccess(_result -> { + if(_result != null) { + $L.put($L, _result); + } + }); + """, cacheFields.get(0), operation.parameters().get(0)); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + builder.add(""" + .doOnSuccess(_result -> { + if(_result != null) { + $L.put($T.of($L), _result); + } + }); + """, cacheFields.get(0), getCacheKey(operation), recordParameters); + } } - return CodeBlock.builder() - .add(""" - var _key = new $L($L); - """, - operation.key().simpleName(), recordParameters) - .add(builder.toString()) - .build(); + return builder.build(); } } diff --git a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheableAopKoraAspect.java b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheableAopKoraAspect.java index de479f6e3..bf0012268 100644 --- a/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheableAopKoraAspect.java +++ b/cache/cache-annotation-processor/src/main/java/ru/tinkoff/kora/cache/annotation/processor/aop/CacheableAopKoraAspect.java @@ -1,21 +1,25 @@ package ru.tinkoff.kora.cache.annotation.processor.aop; +import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; import ru.tinkoff.kora.annotation.processor.common.MethodUtils; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.Cacheables; -import ru.tinkoff.kora.cache.annotation.processor.CacheMeta; +import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException; import ru.tinkoff.kora.cache.annotation.processor.CacheOperation; +import ru.tinkoff.kora.cache.annotation.processor.CacheOperationUtils; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import java.util.List; import java.util.Set; - -import static ru.tinkoff.kora.cache.annotation.processor.CacheOperationManager.getCacheOperation; +import java.util.concurrent.Future; public class CacheableAopKoraAspect extends AbstractAopCacheAspect { + private static final ClassName ANNOTATION_CACHEABLE = ClassName.get("ru.tinkoff.kora.cache.annotation", "Cacheable"); + private static final ClassName ANNOTATION_CACHEABLES = ClassName.get("ru.tinkoff.kora.cache.annotation", "Cacheables"); + private final ProcessingEnvironment env; public CacheableAopKoraAspect(ProcessingEnvironment env) { @@ -24,29 +28,61 @@ public CacheableAopKoraAspect(ProcessingEnvironment env) { @Override public Set getSupportedAnnotationTypes() { - return Set.of(Cacheable.class.getCanonicalName(), Cacheables.class.getCanonicalName()); + return Set.of(ANNOTATION_CACHEABLE.canonicalName(), ANNOTATION_CACHEABLES.canonicalName()); } @Override public ApplyResult apply(ExecutableElement method, String superCall, AspectContext aspectContext) { - final CacheOperation operation = getCacheOperation(method, env); - final CacheMirrors cacheMirrors = getCacheMirrors(operation, method, env); + if (MethodUtils.isFuture(method)) { + throw new ProcessingErrorException("@Cacheable can't be applied for types assignable from " + Future.class, method); + } else if (MethodUtils.isFlux(method)) { + throw new ProcessingErrorException("@Cacheable can't be applied for types assignable from " + Flux.class, method); + } + + final CacheOperation operation = CacheOperationUtils.getCacheMeta(method); + final List cacheFields = getCacheFields(operation, env, aspectContext); - final List cacheFields = getCacheFields(operation, cacheMirrors, aspectContext); final CodeBlock body = MethodUtils.isMono(method) - ? buildBodyMono(method, operation, superCall, cacheFields) - : buildBodySync(method, operation, superCall, cacheFields); + ? buildBodyMono(method, operation, cacheFields, superCall) + : buildBodySync(method, operation, cacheFields, superCall); return new ApplyResult.MethodBody(body); } private CodeBlock buildBodySync(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { - final String recordParameters = getKeyRecordParameters(operation, method); + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); + final CodeBlock keyBlock; + if (operation.parameters().size() == 1) { + keyBlock = CodeBlock.builder() + .add(""" + var _key = $L; + """, + operation.parameters().get(0)) + .build(); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + keyBlock = CodeBlock.builder() + .add(""" + var _key = $T.of($L); + """, + getCacheKey(operation), recordParameters) + .build(); + + } + + if (operation.cacheImplementations().size() == 1) { + return CodeBlock.builder() + .add(keyBlock) + .add(CodeBlock.of(""" + return $L.computeIfAbsent(_key, _k -> $L); + """, cacheFields.get(0), superMethod)) + .build(); + } + final StringBuilder builder = new StringBuilder(); // cache get @@ -77,30 +113,25 @@ private CodeBlock buildBodySync(ExecutableElement method, builder.append("_value = ").append(superMethod).append(";\n"); // cache put - for (int i = 0; i < cacheFields.size(); i++) { - final String cache = cacheFields.get(i); + for (final String cache : cacheFields) { builder.append(cache).append(".put(_key, _value);\n"); } builder.append("return _value;"); return CodeBlock.builder() - .add(""" - var _key = new $L($L); - """, - operation.key().simpleName(), recordParameters) + .add(keyBlock) .add(builder.toString()) .build(); } private CodeBlock buildBodyMono(ExecutableElement method, CacheOperation operation, - String superCall, - List cacheFields) { - final String recordParameters = getKeyRecordParameters(operation, method); + List cacheFields, + String superCall) { final String superMethod = getSuperMethod(method, superCall); // cache variables - final StringBuilder builder = new StringBuilder(); + final CodeBlock.Builder builder = CodeBlock.builder(); // cache get for (int i = 0; i < cacheFields.size(); i++) { @@ -110,67 +141,96 @@ private CodeBlock buildBodyMono(ExecutableElement method, : "_value = _value.switchIfEmpty("; final String getPart = ".getAsync(_key)"; - builder.append(prefix) - .append(cache) - .append(getPart); + builder.add(prefix) + .add(cache) + .add(getPart); // put value from cache into prev level caches if (i > 1) { - builder.append("\n").append(""" - .publishOn(reactor.core.scheduler.Schedulers.boundedElastic()) + builder.add("\n").add(""" + .publishOn($T.boundedElastic()) .doOnSuccess(_fromCache -> { if(_fromCache != null) { - reactor.core.publisher.Flux.merge(java.util.List.of( - """); + $T.merge($T.of( + """, Schedulers.class, Flux.class, List.class); for (int j = 0; j < i; j++) { final String prevCache = cacheFields.get(j); final String suffix = (j == i - 1) ? ".putAsync(_key, _fromCache)\n" : ".putAsync(_key, _fromCache),\n"; - builder.append("\t\t\t\t").append(prevCache).append(suffix); + builder.add("\t\t\t\t").add(prevCache).add(suffix); } - builder.append("\t\t)).then().block();\n}}));\n\n"); + builder.add("\t\t)).then().block();\n}}));\n\n"); } else if (i == 1) { - builder.append("\n\t") - .append(String.format(""" + builder.add("\n\t") + .add(String.format(""" .doOnSuccess(_fromCache -> { if(_fromCache != null) { %s.put(_key, _fromCache); } })); """, cacheFields.get(0))) - .append("\n"); + .add("\n"); } else { - builder.append(";\n"); + builder.add(";\n"); } } // cache super method - builder.append("return _value.switchIfEmpty(").append(superMethod); + builder.add("return _value.switchIfEmpty(").add(superMethod); // cache put if (cacheFields.size() > 1) { - builder.append(".flatMap(_result -> reactor.core.publisher.Flux.merge(java.util.List.of(\n"); + builder.add(".flatMap(_result -> $T.merge($T.of(\n", Flux.class, List.class); for (int i = 0; i < cacheFields.size(); i++) { final String cache = cacheFields.get(i); final String suffix = (i == cacheFields.size() - 1) ? ".putAsync(_key, _result)\n" : ".putAsync(_key, _result),\n"; - builder.append("\t").append(cache).append(suffix); + builder.add("\t").add(cache).add(suffix); } - builder.append(")).then(Mono.just(_result))));"); + builder.add(")).then(Mono.just(_result))));"); } else { - builder.append(".doOnSuccess(_result -> ").append(cacheFields.get(0)).append(".put(_key, _result)));\n"); + if (operation.parameters().size() == 1) { + builder.add(""" + .doOnSuccess(_result -> { + if(_result != null) { + $L.put($L, _result); + } + })); + """, cacheFields.get(0), operation.parameters().get(0)); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + builder.add(""" + .doOnSuccess(_result -> { + if(_result != null) { + $L.put($T.of($L), _result); + } + })); + """, cacheFields.get(0), getCacheKey(operation), recordParameters); + } } - return CodeBlock.builder() - .add(""" - var _key = new $L($L); - """, - operation.key().simpleName(), recordParameters) - .add(builder.toString()) - .build(); + if (operation.parameters().size() == 1) { + return CodeBlock.builder() + .add(""" + var _key = $L; + """, + operation.parameters().get(0)) + .add(builder.build()) + .build(); + } else { + final String recordParameters = getKeyRecordParameters(operation, method); + return CodeBlock.builder() + .add(""" + var _key = $T.of($L); + """, + getCacheKey(operation), recordParameters) + .add(builder.build()) + .build(); + + } } } diff --git a/cache/cache-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/cache/cache-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor index 14577b2e2..ddaa194ee 100644 --- a/cache/cache-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/cache/cache-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1 @@ -ru.tinkoff.kora.cache.annotation.processor.CacheKeyAnnotationProcessor +ru.tinkoff.kora.cache.annotation.processor.CacheAnnotationProcessor diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessorTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessorTests.java new file mode 100644 index 000000000..cf115b60d --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheAnnotationProcessorTests.java @@ -0,0 +1,84 @@ +package ru.tinkoff.kora.cache.annotation.processor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.annotation.processor.common.TestUtils; +import ru.tinkoff.kora.annotation.processor.common.TestUtils.CompilationErrorException; +import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.flux.CacheableFluxWrongGet; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.flux.CacheableWrongFluxPut; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableMonoWrongGetVoid; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableMonoWrongPutVoid; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.publisher.CacheableWrongPublisherGet; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.publisher.CacheableWrongPublisherPut; +import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.*; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CacheAnnotationProcessorTests extends Assertions { + + @Test + void cacheKeyMultipleAnnotationsOneMethod() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongAnnotationMany.class, new AopAnnotationProcessor())); + } + + @Test + void cacheKeyArgumentMissing() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongArgumentMissing.class, new AopAnnotationProcessor())); + } + + @Test + void cacheKeyArgumentWrongOrder() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongArgumentOrder.class, new AopAnnotationProcessor())); + } + + @Test + void cacheKeyArgumentWrongType() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongArgumentType.class, new AopAnnotationProcessor())); + } + + @Test + void cacheNamePatternMismatch() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongName.class, new CacheAnnotationProcessor())); + } + + @Test + void cacheGetForVoidSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongGetVoid.class, new AopAnnotationProcessor())); + } + + @Test + void cachePutForVoidSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableSyncWrongPutVoid.class, new AopAnnotationProcessor())); + } + + @Test + void cacheGetForMonoVoidSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableMonoWrongGetVoid.class, new AopAnnotationProcessor())); + } + + @Test + void cachePutForMonoVoidSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableMonoWrongPutVoid.class, new AopAnnotationProcessor())); + } + + @Test + void cacheGetForFluxSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableFluxWrongGet.class, new AopAnnotationProcessor())); + } + + @Test + void cachePutForFluxSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableWrongFluxPut.class, new AopAnnotationProcessor())); + } + + @Test + void cacheGetForPublisherSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableWrongPublisherGet.class, new AopAnnotationProcessor())); + } + + @Test + void cachePutForPublisherSignature() { + assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableWrongPublisherPut.class, new AopAnnotationProcessor())); + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessorTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessorTests.java deleted file mode 100644 index 8747c60dd..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheKeyAnnotationProcessorTests.java +++ /dev/null @@ -1,104 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import ru.tinkoff.kora.annotation.processor.common.TestUtils; -import ru.tinkoff.kora.annotation.processor.common.TestUtils.CompilationErrorException; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.flux.CacheableTargetGetFlux; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.flux.CacheableTargetPutFlux; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableTargetGetMonoVoid; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableTargetMono; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableTargetPutMonoVoid; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.publisher.CacheableTargetGetPublisher; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.publisher.CacheableTargetPutPublisher; -import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.*; - -class CacheKeyAnnotationProcessorTests extends Assertions { - - @Test - void cacheKeyRecordGeneratedForSync() throws Exception { - var classLoader = TestUtils.annotationProcess(CacheableTargetSync.class, new CacheKeyAnnotationProcessor()); - var record = classLoader.loadClass("ru.tinkoff.kora.cache.annotation.processor.testdata.sync.$CacheKey__sync_cache"); - - org.assertj.core.api.Assertions.assertThat(record) - .isNotNull() - .hasDeclaredMethods("values") - .hasOnlyDeclaredFields("arg1", "arg2"); - } - - @Test - void cacheKeyRecordGeneratedForMono() throws Exception { - var classLoader = TestUtils.annotationProcess(CacheableTargetMono.class, new CacheKeyAnnotationProcessor()); - var record = classLoader.loadClass("ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheKey__mono_cache"); - - org.assertj.core.api.Assertions.assertThat(record) - .isNotNull() - .hasDeclaredMethods("values") - .hasOnlyDeclaredFields("arg1", "arg2"); - } - - @Test - void cacheKeyMultipleAnnotationsOneMethod() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetAnnotationMultiple.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheKeyArgumentMissing() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetArgumentMissing.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheKeyArgumentWrongOrder() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetArgumentWrongOrder.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheKeyArgumentWrongType() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetArgumentWrongType.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheNamePatternMismatch() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetNameInvalid.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheGetForVoidSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetGetVoid.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cachePutForVoidSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetPutVoid.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheGetForMonoVoidSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetGetMonoVoid.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cachePutForMonoVoidSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetPutMonoVoid.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheGetForFluxSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetGetFlux.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cachePutForFluxSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetPutFlux.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cacheGetForPublisherSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetGetPublisher.class, new CacheKeyAnnotationProcessor())); - } - - @Test - void cachePutForPublisherSignature() { - assertThrows(CompilationErrorException.class, () -> TestUtils.annotationProcess(CacheableTargetPutPublisher.class, new CacheKeyAnnotationProcessor())); - } -} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheRunner.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheRunner.java new file mode 100644 index 000000000..01b325745 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/CacheRunner.java @@ -0,0 +1,33 @@ +package ru.tinkoff.kora.cache.annotation.processor; + +import org.jetbrains.annotations.Nullable; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; + +import java.time.Duration; + +final class CacheRunner { + + private CacheRunner() { } + + public static CaffeineCacheConfig getConfig() { + return new CaffeineCacheConfig() { + @Nullable + @Override + public Duration expireAfterWrite() { + return null; + } + + @Nullable + @Override + public Duration expireAfterAccess() { + return null; + } + + @Nullable + @Override + public Integer initialSize() { + return null; + } + }; + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheAopTests.java index b55cbaecc..ecfc6ad16 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheAopTests.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheAopTests.java @@ -6,32 +6,53 @@ import org.junit.jupiter.api.TestInstance; import ru.tinkoff.kora.annotation.processor.common.TestUtils; import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableTargetMono; +import ru.tinkoff.kora.cache.CacheKey; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableMono; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; +import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.time.Duration; +import java.util.List; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MonoCacheAopTests extends Assertions { +class MonoCacheAopTests extends Assertions implements CaffeineCacheModule { - public static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheableTargetMono__AopProxy"; + private static final String CACHED_IMPL = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache2Impl"; + private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheableMono__AopProxy"; - private final DummyCacheManager cacheManager = new DummyCacheManager<>(); - private CacheableTargetMono service = null; + private DummyCache2 cache = null; + private CacheableMono service = null; - private CacheableTargetMono getService(DummyCacheManager manager) { + private CacheableMono getService() { if (service != null) { return service; } try { - var classLoader = TestUtils.annotationProcess(CacheableTargetMono.class, new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor()); + var classLoader = TestUtils.annotationProcess(List.of(DummyCache2.class, CacheableMono.class), + new AopAnnotationProcessor(), new CacheAnnotationProcessor()); + + var cacheClass = classLoader.loadClass(CACHED_IMPL); + if (cacheClass == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); + } + + final Constructor cacheConstructor = cacheClass.getDeclaredConstructors()[0]; + cacheConstructor.setAccessible(true); + cache = (DummyCache2) cacheConstructor.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + var serviceClass = classLoader.loadClass(CACHED_SERVICE); if (serviceClass == null) { throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); } - service = (CacheableTargetMono) serviceClass.getConstructors()[0].newInstance(manager); + + final Constructor serviceConstructor = serviceClass.getDeclaredConstructors()[0]; + serviceConstructor.setAccessible(true); + service = (CacheableMono) serviceConstructor.newInstance(cache); return service; } catch (Exception e) { throw new IllegalStateException(e); @@ -39,14 +60,16 @@ private CacheableTargetMono getService(DummyCacheManager manager) { } @BeforeEach - void reset() { - cacheManager.reset(); + void cleanup() { + if (cache != null) { + cache.invalidateAll(); + } } @Test void getFromCacheWhenWasCacheEmpty() { // given - final CacheableTargetMono service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -63,7 +86,7 @@ void getFromCacheWhenWasCacheEmpty() { @Test void getFromCacheWhenCacheFilled() { // given - final CacheableTargetMono service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -81,7 +104,7 @@ void getFromCacheWhenCacheFilled() { @Test void getFromCacheWrongKeyWhenCacheFilled() { // given - final CacheableTargetMono service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -100,7 +123,7 @@ void getFromCacheWrongKeyWhenCacheFilled() { @Test void getFromCacheWhenCacheFilledOtherKey() { // given - final CacheableTargetMono service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -119,7 +142,7 @@ void getFromCacheWhenCacheFilledOtherKey() { @Test void getFromCacheWhenCacheInvalidate() { // given - final CacheableTargetMono service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -127,10 +150,17 @@ void getFromCacheWhenCacheInvalidate() { final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); assertEquals(initial, cached); + + final String cached2 = service.putValue(BigDecimal.ZERO, "5", "2").block(Duration.ofMinutes(1)); + assertEquals(initial, cached2); + service.value = "2"; service.evictValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); // then + assertNull(cache.get(CacheKey.of("1", BigDecimal.ZERO))); + assertEquals(cached2, cache.get(CacheKey.of("2", BigDecimal.ZERO))); + final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); assertNotEquals(cached, fromCache); } @@ -138,7 +168,7 @@ void getFromCacheWhenCacheInvalidate() { @Test void getFromCacheWhenCacheInvalidateAll() { // given - final CacheableTargetMono service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -146,10 +176,17 @@ void getFromCacheWhenCacheInvalidateAll() { final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); assertEquals(initial, cached); + + final String cached2 = service.putValue(BigDecimal.ZERO, "5", "2").block(Duration.ofMinutes(1)); + assertEquals(initial, cached2); + service.value = "2"; service.evictAll().block(Duration.ofMinutes(1)); // then + assertNull(cache.get(CacheKey.of("1", BigDecimal.ZERO))); + assertNull(cache.get(CacheKey.of("2", BigDecimal.ZERO))); + final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); assertNotEquals(cached, fromCache); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheOneAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheOneAopTests.java new file mode 100644 index 000000000..5cb404064 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoCacheOneAopTests.java @@ -0,0 +1,178 @@ +package ru.tinkoff.kora.cache.annotation.processor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.annotation.processor.common.TestUtils; +import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache1; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableMonoOne; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; + +import java.lang.reflect.Constructor; +import java.math.BigDecimal; +import java.time.Duration; +import java.util.List; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MonoCacheOneAopTests extends Assertions implements CaffeineCacheModule { + + private static final String CACHED_IMPL = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache1Impl"; + private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheableMonoOne__AopProxy"; + + private DummyCache1 cache = null; + private CacheableMonoOne service = null; + + private CacheableMonoOne getService() { + if (service != null) { + return service; + } + + try { + var classLoader = TestUtils.annotationProcess(List.of(DummyCache1.class, CacheableMonoOne.class), + new AopAnnotationProcessor(), new CacheAnnotationProcessor()); + + var cacheClass = classLoader.loadClass(CACHED_IMPL); + if (cacheClass == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); + } + + final Constructor cacheConstructor = cacheClass.getDeclaredConstructors()[0]; + cacheConstructor.setAccessible(true); + cache = (DummyCache1) cacheConstructor.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + + var serviceClass = classLoader.loadClass(CACHED_SERVICE); + if (serviceClass == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); + } + + final Constructor serviceConstructor = serviceClass.getDeclaredConstructors()[0]; + serviceConstructor.setAccessible(true); + service = (CacheableMonoOne) serviceConstructor.newInstance(cache); + return service; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @BeforeEach + void cleanup() { + if (cache != null) { + cache.invalidateAll(); + } + } + + @Test + void getFromCacheWhenWasCacheEmpty() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String notCached = service.getValue("1").block(Duration.ofMinutes(1)); + service.value = "2"; + + // then + final String fromCache = service.getValue("1").block(Duration.ofMinutes(1)); + assertEquals(notCached, fromCache); + assertNotEquals("2", fromCache); + } + + @Test + void getFromCacheWhenCacheFilled() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1").block(Duration.ofMinutes(1)); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); + assertEquals(initial, cached); + service.value = "2"; + + // then + final String fromCache = service.getValue("1").block(Duration.ofMinutes(1)); + assertEquals(cached, fromCache); + } + + @Test + void getFromCacheWrongKeyWhenCacheFilled() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1").block(Duration.ofMinutes(1)); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); + assertEquals(initial, cached); + service.value = "2"; + + // then + final String fromCache = service.getValue("2").block(Duration.ofMinutes(1)); + assertNotEquals(cached, fromCache); + assertEquals(service.value, fromCache); + } + + @Test + void getFromCacheWhenCacheFilledOtherKey() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); + service.value = "2"; + final String initial = service.getValue("2").block(Duration.ofMinutes(1)); + assertNotEquals(cached, initial); + + // then + final String fromCache = service.getValue("2").block(Duration.ofMinutes(1)); + assertNotEquals(cached, fromCache); + assertEquals(initial, fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidate() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1").block(Duration.ofMinutes(1)); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); + assertEquals(initial, cached); + service.value = "2"; + service.evictValue("1").block(Duration.ofMinutes(1)); + + // then + final String fromCache = service.getValue("1").block(Duration.ofMinutes(1)); + assertNotEquals(cached, fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidateAll() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1").block(Duration.ofMinutes(1)); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); + assertEquals(initial, cached); + service.value = "2"; + service.evictAll().block(Duration.ofMinutes(1)); + + // then + final String fromCache = service.getValue("1").block(Duration.ofMinutes(1)); + assertNotEquals(cached, fromCache); + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyCacheAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyCacheAopTests.java index 55f61604d..38c5422ab 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyCacheAopTests.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyCacheAopTests.java @@ -1,5 +1,6 @@ package ru.tinkoff.kora.cache.annotation.processor; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -7,37 +8,65 @@ import ru.tinkoff.kora.annotation.processor.common.TestUtils; import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; import ru.tinkoff.kora.cache.CacheKey; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableTargetMonoMany; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache22; +import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableMonoMany; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; -import javax.annotation.Nonnull; +import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.time.Duration; -import java.util.Arrays; import java.util.List; -@SuppressWarnings({"rawtypes", "unchecked"}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MonoManyCacheAopTests extends Assertions { +class MonoManyCacheAopTests extends Assertions implements CaffeineCacheModule { - public static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheableTargetMonoMany__AopProxy"; + private static final String CACHED_IMPL_1 = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache2Impl"; + private static final String CACHED_IMPL_2 = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache22Impl"; + private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheableMonoMany__AopProxy"; - private final DummyCacheManager cacheManager = new DummyCacheManager<>(); - private CacheableTargetMonoMany service = null; + private DummyCache2 cache1 = null; + private DummyCache22 cache2 = null; + private CacheableMonoMany service = null; - private CacheableTargetMonoMany getService(DummyCacheManager manager) { + private CacheableMonoMany getService() { if (service != null) { return service; } try { - var classLoader = TestUtils.annotationProcess(CacheableTargetMonoMany.class, new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor()); + var classLoader = TestUtils.annotationProcess(List.of(DummyCache2.class, DummyCache22.class, CacheableMonoMany.class), + new AopAnnotationProcessor(), new CacheAnnotationProcessor()); + + var cacheClass1 = classLoader.loadClass(CACHED_IMPL_1); + if (cacheClass1 == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_IMPL_1); + } + + final Constructor cacheConstructor1 = cacheClass1.getDeclaredConstructors()[0]; + cacheConstructor1.setAccessible(true); + cache1 = (DummyCache2) cacheConstructor1.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + + var cacheClass2 = classLoader.loadClass(CACHED_IMPL_2); + if (cacheClass2 == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_IMPL_2); + } + + final Constructor cacheConstructor2 = cacheClass2.getDeclaredConstructors()[0]; + cacheConstructor2.setAccessible(true); + cache2 = (DummyCache22) cacheConstructor2.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + var serviceClass = classLoader.loadClass(CACHED_SERVICE); if (serviceClass == null) { throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); } - service = (CacheableTargetMonoMany) serviceClass.getConstructors()[0].newInstance(manager); + + final Constructor serviceConstructor = serviceClass.getDeclaredConstructors()[0]; + serviceConstructor.setAccessible(true); + service = (CacheableMonoMany) serviceConstructor.newInstance(cache1, cache2); return service; } catch (Exception e) { throw new IllegalStateException(e); @@ -45,14 +74,17 @@ private CacheableTargetMonoMany getService(DummyCacheManager manager) { } @BeforeEach - void reset() { - cacheManager.reset(); + void cleanup() { + if (cache1 != null && cache2 != null) { + cache1.invalidateAll(); + cache2.invalidateAll(); + } } @Test void getFromCacheWhenWasCacheEmpty() { // given - final CacheableTargetMonoMany service = getService(cacheManager); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); @@ -69,30 +101,12 @@ void getFromCacheWhenWasCacheEmpty() { @Test void getFromCacheLevel2AndThenSaveCacheLevel1() { // given - final CacheableTargetMonoMany service = getService(cacheManager); - final DummyCache cache1 = cacheManager.getCache("mono_cache"); - final DummyCache cache2 = cacheManager.getCache("mono_cache_2"); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); - assertTrue(cache1.isEmpty()); - assertTrue(cache2.isEmpty()); var cachedValue = "LEVEL_2"; - var cachedKey = new CacheKey() { - @Nonnull - @Override - public List values() { - return Arrays.asList("1", BigDecimal.ZERO); - } - - @Override - public String toString() { - return "1" + "-" + BigDecimal.ZERO; - } - }; - - cache2.put(cachedKey, cachedValue); - assertFalse(cache2.isEmpty()); + cache2.put(CacheKey.of("1", BigDecimal.ZERO), cachedValue); // when final String valueFromLevel2 = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); @@ -102,14 +116,12 @@ public String toString() { final String valueFromLevel1 = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); assertEquals(valueFromLevel2, valueFromLevel1); assertEquals(cachedValue, valueFromLevel1); - assertFalse(cache1.isEmpty()); - assertFalse(cache2.isEmpty()); } @Test void getFromCacheWhenCacheFilled() { // given - final CacheableTargetMonoMany service = getService(cacheManager); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); @@ -127,7 +139,7 @@ void getFromCacheWhenCacheFilled() { @Test void getFromCacheWrongKeyWhenCacheFilled() { // given - final CacheableTargetMonoMany service = getService(cacheManager); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); @@ -146,7 +158,7 @@ void getFromCacheWrongKeyWhenCacheFilled() { @Test void getFromCacheWhenCacheFilledOtherKey() { // given - final CacheableTargetMonoMany service = getService(cacheManager); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); @@ -165,7 +177,7 @@ void getFromCacheWhenCacheFilledOtherKey() { @Test void getFromCacheWhenCacheInvalidate() { // given - final CacheableTargetMonoMany service = getService(cacheManager); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); @@ -184,7 +196,7 @@ void getFromCacheWhenCacheInvalidate() { @Test void getFromCacheWhenCacheInvalidateAll() { // given - final CacheableTargetMonoMany service = getService(cacheManager); + final CacheableMonoMany service = getService(); service.value = "1"; assertNotNull(service); diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyManyCacheAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyManyCacheAopTests.java deleted file mode 100644 index 827e10834..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/MonoManyManyCacheAopTests.java +++ /dev/null @@ -1,248 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import ru.tinkoff.kora.annotation.processor.common.TestUtils; -import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; -import ru.tinkoff.kora.cache.CacheKey; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; -import ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.CacheableTargetMonoManyMany; - -import javax.annotation.Nonnull; -import java.math.BigDecimal; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -@SuppressWarnings({"rawtypes", "unchecked"}) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MonoManyManyCacheAopTests extends Assertions { - - public static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono.$CacheableTargetMonoManyMany__AopProxy"; - - private final DummyCacheManager cacheManager = new DummyCacheManager<>(); - private CacheableTargetMonoManyMany service = null; - - private CacheableTargetMonoManyMany getService(DummyCacheManager manager) { - if (service != null) { - return service; - } - - try { - var classLoader = TestUtils.annotationProcess(CacheableTargetMonoManyMany.class, new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor()); - var serviceClass = classLoader.loadClass(CACHED_SERVICE); - if (serviceClass == null) { - throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); - } - service = (CacheableTargetMonoManyMany) serviceClass.getConstructors()[0].newInstance(manager); - return service; - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @BeforeEach - void reset() { - cacheManager.reset(); - } - - @Test - void getFromCacheWhenWasCacheEmpty() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - service.value = "1"; - assertNotNull(service); - - // when - final String notCached = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - service.value = "2"; - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(notCached, fromCache); - assertNotEquals("2", fromCache); - } - - @Test - void getFromCacheLevel3AndThenSaveCacheLevel1AndLevel2() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - final DummyCache cache1 = cacheManager.getCache("mono_cache"); - final DummyCache cache2 = cacheManager.getCache("mono_cache_2"); - final DummyCache cache3 = cacheManager.getCache("mono_cache_3"); - service.value = "1"; - assertNotNull(service); - assertTrue(cache1.isEmpty()); - assertTrue(cache2.isEmpty()); - assertTrue(cache3.isEmpty()); - - var cachedValue = "LEVEL_3"; - var cachedKey = new CacheKey() { - @Nonnull - @Override - public List values() { - return Arrays.asList("1", BigDecimal.ZERO); - } - - @Override - public String toString() { - return "1" + "-" + BigDecimal.ZERO; - } - }; - - cache3.put(cachedKey, cachedValue); - assertFalse(cache3.isEmpty()); - - // when - final String valueFromLevel3 = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - service.value = "2"; - - // then - final String valueFromLevel1 = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(valueFromLevel3, valueFromLevel1); - assertEquals(cachedValue, valueFromLevel1); - assertFalse(cache1.isEmpty()); - assertFalse(cache2.isEmpty()); - assertFalse(cache3.isEmpty()); - } - - @Test - void getFromCacheLevel2AndThenSaveCacheLevel1() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - final DummyCache cache1 = cacheManager.getCache("mono_cache"); - final DummyCache cache2 = cacheManager.getCache("mono_cache_2"); - final DummyCache cache3 = cacheManager.getCache("mono_cache_3"); - service.value = "1"; - assertNotNull(service); - assertTrue(cache1.isEmpty()); - assertTrue(cache2.isEmpty()); - assertTrue(cache3.isEmpty()); - - var cachedValue = "LEVEL_2"; - var cachedKey = new CacheKey() { - @Nonnull - @Override - public List values() { - return Arrays.asList("1", BigDecimal.ZERO); - } - - @Override - public String toString() { - return "1" + "-" + BigDecimal.ZERO; - } - }; - - cache2.put(cachedKey, cachedValue); - assertFalse(cache2.isEmpty()); - - // when - final String valueFromLevel2 = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - service.value = "2"; - - // then - final String valueFromLevel1 = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(valueFromLevel2, valueFromLevel1); - assertEquals(cachedValue, valueFromLevel1); - assertFalse(cache1.isEmpty()); - assertFalse(cache2.isEmpty()); - assertTrue(cache3.isEmpty()); - } - - @Test - void getFromCacheWhenCacheFilled() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(cached, fromCache); - } - - @Test - void getFromCacheWrongKeyWhenCacheFilled() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - - // then - final String fromCache = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - assertEquals(service.value, fromCache); - } - - @Test - void getFromCacheWhenCacheFilledOtherKey() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - service.value = "1"; - assertNotNull(service); - - // when - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - service.value = "2"; - final String initial = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, initial); - - // then - final String fromCache = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - assertEquals(initial, fromCache); - } - - @Test - void getFromCacheWhenCacheInvalidate() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - service.evictValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - } - - @Test - void getFromCacheWhenCacheInvalidateAll() { - // given - final CacheableTargetMonoManyMany service = getService(cacheManager); - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - service.evictAll().block(Duration.ofMinutes(1)); - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - } -} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheAopTests.java index 22b804c8e..1cffb01cb 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheAopTests.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheAopTests.java @@ -6,31 +6,52 @@ import org.junit.jupiter.api.TestInstance; import ru.tinkoff.kora.annotation.processor.common.TestUtils; import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; -import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.CacheableTargetSync; +import ru.tinkoff.kora.cache.CacheKey; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; +import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.CacheableSync; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; +import java.lang.reflect.Constructor; import java.math.BigDecimal; +import java.util.List; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class SyncCacheAopTests extends Assertions { +class SyncCacheAopTests extends Assertions implements CaffeineCacheModule { - private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.sync.$CacheableTargetSync__AopProxy"; + private static final String CACHED_IMPL = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache2Impl"; + private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.sync.$CacheableSync__AopProxy"; - private final DummyCacheManager cacheManager = new DummyCacheManager<>(); - private CacheableTargetSync service = null; + private DummyCache2 cache = null; + private CacheableSync service = null; - private CacheableTargetSync getService(DummyCacheManager manager) { + private CacheableSync getService() { if (service != null) { return service; } try { - var classLoader = TestUtils.annotationProcess(CacheableTargetSync.class, new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor()); + var classLoader = TestUtils.annotationProcess(List.of(DummyCache2.class, CacheableSync.class), + new AopAnnotationProcessor(), new CacheAnnotationProcessor()); + + var cacheClass = classLoader.loadClass(CACHED_IMPL); + if (cacheClass == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); + } + + final Constructor cacheConstructor = cacheClass.getDeclaredConstructors()[0]; + cacheConstructor.setAccessible(true); + cache = (DummyCache2) cacheConstructor.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + var serviceClass = classLoader.loadClass(CACHED_SERVICE); if (serviceClass == null) { throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); } - service = (CacheableTargetSync) serviceClass.getConstructors()[0].newInstance(manager); + + final Constructor serviceConstructor = serviceClass.getDeclaredConstructors()[0]; + serviceConstructor.setAccessible(true); + service = (CacheableSync) serviceConstructor.newInstance(cache); return service; } catch (Exception e) { throw new IllegalStateException(e); @@ -38,14 +59,16 @@ private CacheableTargetSync getService(DummyCacheManager manager) { } @BeforeEach - void reset() { - cacheManager.reset(); + void cleanup() { + if (cache != null) { + cache.invalidateAll(); + } } @Test void getFromCacheWhenWasCacheEmpty() { // given - final CacheableTargetSync service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -62,7 +85,7 @@ void getFromCacheWhenWasCacheEmpty() { @Test void getFromCacheWhenCacheFilled() { // given - final CacheableTargetSync service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -80,7 +103,7 @@ void getFromCacheWhenCacheFilled() { @Test void getFromCacheWrongKeyWhenCacheFilled() { // given - final CacheableTargetSync service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -99,7 +122,7 @@ void getFromCacheWrongKeyWhenCacheFilled() { @Test void getFromCacheWhenCacheFilledOtherKey() { // given - final CacheableTargetSync service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -118,7 +141,7 @@ void getFromCacheWhenCacheFilledOtherKey() { @Test void getFromCacheWhenCacheInvalidate() { // given - final CacheableTargetSync service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -126,10 +149,17 @@ void getFromCacheWhenCacheInvalidate() { final String initial = service.getValue("1", BigDecimal.ZERO); final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); assertEquals(initial, cached); + + final String cached2 = service.putValue(BigDecimal.ZERO, "5", "2"); + assertEquals(initial, cached2); + service.value = "2"; service.evictValue("1", BigDecimal.ZERO); // then + assertNull(cache.get(CacheKey.of("1", BigDecimal.ZERO))); + assertEquals(cached2, cache.get(CacheKey.of("2", BigDecimal.ZERO))); + final String fromCache = service.getValue("1", BigDecimal.ZERO); assertNotEquals(cached, fromCache); } @@ -137,7 +167,7 @@ void getFromCacheWhenCacheInvalidate() { @Test void getFromCacheWhenCacheInvalidateAll() { // given - final CacheableTargetSync service = getService(cacheManager); + var service = getService(); service.value = "1"; assertNotNull(service); @@ -145,10 +175,17 @@ void getFromCacheWhenCacheInvalidateAll() { final String initial = service.getValue("1", BigDecimal.ZERO); final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); assertEquals(initial, cached); + + final String cached2 = service.putValue(BigDecimal.ZERO, "5", "2"); + assertEquals(initial, cached2); + service.value = "2"; service.evictAll(); // then + assertNull(cache.get(CacheKey.of("1", BigDecimal.ZERO))); + assertNull(cache.get(CacheKey.of("2", BigDecimal.ZERO))); + final String fromCache = service.getValue("1", BigDecimal.ZERO); assertNotEquals(cached, fromCache); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheOneAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheOneAopTests.java new file mode 100644 index 000000000..aa58832b6 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncCacheOneAopTests.java @@ -0,0 +1,177 @@ +package ru.tinkoff.kora.cache.annotation.processor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.annotation.processor.common.TestUtils; +import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache1; +import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.CacheableSyncOne; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; + +import java.lang.reflect.Constructor; +import java.math.BigDecimal; +import java.util.List; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SyncCacheOneAopTests extends Assertions implements CaffeineCacheModule { + + private static final String CACHED_IMPL = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache1Impl"; + private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.sync.$CacheableSyncOne__AopProxy"; + + private DummyCache1 cache = null; + private CacheableSyncOne service = null; + + private CacheableSyncOne getService() { + if (service != null) { + return service; + } + + try { + var classLoader = TestUtils.annotationProcess(List.of(DummyCache1.class, CacheableSyncOne.class), + new AopAnnotationProcessor(), new CacheAnnotationProcessor()); + + var cacheClass = classLoader.loadClass(CACHED_IMPL); + if (cacheClass == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); + } + + final Constructor cacheConstructor = cacheClass.getDeclaredConstructors()[0]; + cacheConstructor.setAccessible(true); + cache = (DummyCache1) cacheConstructor.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + + var serviceClass = classLoader.loadClass(CACHED_SERVICE); + if (serviceClass == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); + } + + final Constructor serviceConstructor = serviceClass.getDeclaredConstructors()[0]; + serviceConstructor.setAccessible(true); + service = (CacheableSyncOne) serviceConstructor.newInstance(cache); + return service; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @BeforeEach + void cleanup() { + if (cache != null) { + cache.invalidateAll(); + } + } + + @Test + void getFromCacheWhenWasCacheEmpty() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String notCached = service.getValue("1"); + service.value = "2"; + + // then + final String fromCache = service.getValue("1"); + assertEquals(notCached, fromCache); + assertNotEquals("2", fromCache); + } + + @Test + void getFromCacheWhenCacheFilled() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1"); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); + assertEquals(initial, cached); + service.value = "2"; + + // then + final String fromCache = service.getValue("1"); + assertEquals(cached, fromCache); + } + + @Test + void getFromCacheWrongKeyWhenCacheFilled() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1"); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); + assertEquals(initial, cached); + service.value = "2"; + + // then + final String fromCache = service.getValue("2"); + assertNotEquals(cached, fromCache); + assertEquals(service.value, fromCache); + } + + @Test + void getFromCacheWhenCacheFilledOtherKey() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); + service.value = "2"; + final String initial = service.getValue("2"); + assertNotEquals(cached, initial); + + // then + final String fromCache = service.getValue("2"); + assertNotEquals(cached, fromCache); + assertEquals(initial, fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidate() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1"); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); + assertEquals(initial, cached); + service.value = "2"; + service.evictValue("1"); + + // then + final String fromCache = service.getValue("1"); + assertNotEquals(cached, fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidateAll() { + // given + var service = getService(); + service.value = "1"; + assertNotNull(service); + + // when + final String initial = service.getValue("1"); + final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); + assertEquals(initial, cached); + service.value = "2"; + service.evictAll(); + + // then + final String fromCache = service.getValue("1"); + assertNotEquals(cached, fromCache); + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncManyCacheAopTests.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncManyCacheAopTests.java index bd059f056..f1e91eb47 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncManyCacheAopTests.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/SyncManyCacheAopTests.java @@ -7,36 +7,64 @@ import ru.tinkoff.kora.annotation.processor.common.TestUtils; import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; import ru.tinkoff.kora.cache.CacheKey; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; -import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.CacheableTargetSyncMany; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache22; +import ru.tinkoff.kora.cache.annotation.processor.testdata.sync.CacheableSyncMany; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; -import javax.annotation.Nonnull; +import java.lang.reflect.Constructor; import java.math.BigDecimal; -import java.util.Arrays; import java.util.List; -@SuppressWarnings({"rawtypes", "unchecked"}) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -class SyncManyCacheAopTests extends Assertions { +class SyncManyCacheAopTests extends Assertions implements CaffeineCacheModule { - private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.sync.$CacheableTargetSyncMany__AopProxy"; + private static final String CACHED_IMPL_1 = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache2Impl"; + private static final String CACHED_IMPL_2 = "ru.tinkoff.kora.cache.annotation.processor.testcache.$DummyCache22Impl"; + private static final String CACHED_SERVICE = "ru.tinkoff.kora.cache.annotation.processor.testdata.sync.$CacheableSyncMany__AopProxy"; - private final DummyCacheManager cacheManager = new DummyCacheManager<>(); - private CacheableTargetSyncMany service = null; + private DummyCache2 cache1 = null; + private DummyCache22 cache2 = null; + private CacheableSyncMany service = null; - private CacheableTargetSyncMany getService(DummyCacheManager manager) { + private CacheableSyncMany getService() { if (service != null) { return service; } try { - var classLoader = TestUtils.annotationProcess(CacheableTargetSyncMany.class, new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor()); + var classLoader = TestUtils.annotationProcess(List.of(DummyCache2.class, DummyCache22.class, CacheableSyncMany.class), + new AopAnnotationProcessor(), new CacheAnnotationProcessor()); + + var cacheClass1 = classLoader.loadClass(CACHED_IMPL_1); + if (cacheClass1 == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_IMPL_1); + } + + final Constructor cacheConstructor1 = cacheClass1.getDeclaredConstructors()[0]; + cacheConstructor1.setAccessible(true); + cache1 = (DummyCache2) cacheConstructor1.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + + var cacheClass2 = classLoader.loadClass(CACHED_IMPL_2); + if (cacheClass2 == null) { + throw new IllegalArgumentException("Expected class not found: " + CACHED_IMPL_2); + } + + final Constructor cacheConstructor2 = cacheClass2.getDeclaredConstructors()[0]; + cacheConstructor2.setAccessible(true); + cache2 = (DummyCache22) cacheConstructor2.newInstance(CacheRunner.getConfig(), + caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); + var serviceClass = classLoader.loadClass(CACHED_SERVICE); if (serviceClass == null) { throw new IllegalArgumentException("Expected class not found: " + CACHED_SERVICE); } - service = (CacheableTargetSyncMany) serviceClass.getConstructors()[0].newInstance(manager, manager); + + final Constructor serviceConstructor = serviceClass.getDeclaredConstructors()[0]; + serviceConstructor.setAccessible(true); + service = (CacheableSyncMany) serviceConstructor.newInstance(cache1, cache2); return service; } catch (Exception e) { throw new IllegalStateException(e); @@ -44,14 +72,17 @@ private CacheableTargetSyncMany getService(DummyCacheManager manager) { } @BeforeEach - void reset() { - cacheManager.reset(); + void cleanup() { + if (cache1 != null && cache2 != null) { + cache1.invalidateAll(); + cache2.invalidateAll(); + } } @Test void getFromCacheWhenWasCacheEmpty() { // given - final CacheableTargetSyncMany service = getService(cacheManager); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); @@ -69,30 +100,12 @@ void getFromCacheWhenWasCacheEmpty() { @Test void getFromCacheLevel2AndThenSaveCacheLevel1() { // given - final CacheableTargetSyncMany service = getService(cacheManager); - final DummyCache cache1 = cacheManager.getCache("sync_cache"); - final DummyCache cache2 = cacheManager.getCache("sync_cache_2"); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); - assertTrue(cache1.isEmpty()); - assertTrue(cache2.isEmpty()); var cachedValue = "LEVEL_2"; - var cachedKey = new CacheKey() { - @Nonnull - @Override - public List values() { - return Arrays.asList("1", BigDecimal.ZERO); - } - - @Override - public String toString() { - return "1" + "-" + BigDecimal.ZERO; - } - }; - - cache2.put(cachedKey, cachedValue); - assertFalse(cache2.isEmpty()); + cache2.put(CacheKey.of("1", BigDecimal.ZERO), cachedValue); // when final String valueFromLevel2 = service.getValue("1", BigDecimal.ZERO); @@ -102,14 +115,12 @@ public String toString() { final String valueFromLevel1 = service.getValue("1", BigDecimal.ZERO); assertEquals(valueFromLevel2, valueFromLevel1); assertEquals(cachedValue, valueFromLevel1); - assertFalse(cache1.isEmpty()); - assertFalse(cache2.isEmpty()); } @Test void getFromCacheWhenCacheFilled() { // given - final CacheableTargetSyncMany service = getService(cacheManager); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); @@ -127,7 +138,7 @@ void getFromCacheWhenCacheFilled() { @Test void getFromCacheWrongKeyWhenCacheFilled() { // given - final CacheableTargetSyncMany service = getService(cacheManager); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); @@ -146,7 +157,7 @@ void getFromCacheWrongKeyWhenCacheFilled() { @Test void getFromCacheWhenCacheFilledOtherKey() { // given - final CacheableTargetSyncMany service = getService(cacheManager); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); @@ -165,7 +176,7 @@ void getFromCacheWhenCacheFilledOtherKey() { @Test void getFromCacheWhenCacheInvalidate() { // given - final CacheableTargetSyncMany service = getService(cacheManager); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); @@ -184,7 +195,7 @@ void getFromCacheWhenCacheInvalidate() { @Test void getFromCacheWhenCacheInvalidateAll() { // given - final CacheableTargetSyncMany service = getService(cacheManager); + final CacheableSyncMany service = getService(); service.value = "1"; assertNotNull(service); diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache.java deleted file mode 100644 index 2dd6926d0..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache.java +++ /dev/null @@ -1,86 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor.testcache; - -import reactor.core.publisher.Mono; -import ru.tinkoff.kora.cache.Cache; -import ru.tinkoff.kora.cache.LoadableCache; - -import javax.annotation.Nonnull; -import java.util.HashMap; -import java.util.Map; - -public class DummyCache implements Cache, LoadableCache { - - private final String name; - private final Map cache = new HashMap<>(); - - public DummyCache(String name) { - this.name = name; - } - - @Nonnull - public String origin() { - return "dummy"; - } - - @Nonnull - public String name() { - return name; - } - - @Override - public V get(@Nonnull K key) { - return cache.get(key.toString()); - } - - @Nonnull - @Override - public V put(@Nonnull K key, @Nonnull V value) { - cache.put(key.toString(), value); - return value; - } - - @Override - public void invalidate(@Nonnull K key) { - cache.remove(key.toString()); - } - - @Override - public void invalidateAll() { - cache.clear(); - } - - @Nonnull - @Override - public Mono getAsync(@Nonnull K key) { - return Mono.justOrEmpty(get(key)); - } - - @Nonnull - @Override - public Mono putAsync(@Nonnull K key, @Nonnull V value) { - put(key, value); - return Mono.just(value); - } - - @Nonnull - @Override - public Mono invalidateAsync(@Nonnull K key) { - invalidate(key); - return Mono.empty(); - } - - @Nonnull - @Override - public Mono invalidateAllAsync() { - invalidateAll(); - return Mono.empty(); - } - - public void reset() { - cache.clear();; - } - - public boolean isEmpty() { - return cache.isEmpty(); - } -} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache1.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache1.java new file mode 100644 index 000000000..aabeeb317 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache1.java @@ -0,0 +1,9 @@ +package ru.tinkoff.kora.cache.annotation.processor.testcache; + +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; + +@Cache("dummy") +public interface DummyCache1 extends CaffeineCache { + +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache2.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache2.java new file mode 100644 index 000000000..7560ed960 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache2.java @@ -0,0 +1,12 @@ +package ru.tinkoff.kora.cache.annotation.processor.testcache; + +import ru.tinkoff.kora.cache.CacheKey; +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; + +import java.math.BigDecimal; + +@Cache("dummy") +public interface DummyCache2 extends CaffeineCache, String> { + +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache22.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache22.java new file mode 100644 index 000000000..4761baa6f --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCache22.java @@ -0,0 +1,12 @@ +package ru.tinkoff.kora.cache.annotation.processor.testcache; + +import ru.tinkoff.kora.cache.CacheKey; +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; + +import java.math.BigDecimal; + +@Cache("dummy") +public interface DummyCache22 extends CaffeineCache, String> { + +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCacheManager.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCacheManager.java deleted file mode 100644 index a5cbb9616..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testcache/DummyCacheManager.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor.testcache; - -import ru.tinkoff.kora.cache.CacheManager; - -import javax.annotation.Nonnull; -import java.util.HashMap; -import java.util.Map; - -public class DummyCacheManager implements CacheManager { - - private final Map> cacheMap = new HashMap<>(); - - @Override - public DummyCache getCache(@Nonnull String name) { - return cacheMap.computeIfAbsent(name, k -> new DummyCache<>(name)); - } - - public void reset() { - cacheMap.values().forEach(DummyCache::reset); - } -} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableTargetGetFlux.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableFluxWrongGet.java similarity index 73% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableTargetGetFlux.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableFluxWrongGet.java index f50a90d88..fd6c7c971 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableTargetGetFlux.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableFluxWrongGet.java @@ -3,20 +3,20 @@ import reactor.core.publisher.Flux; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetGetFlux { +public class CacheableFluxWrongGet { public String value = "1"; - @Cacheable(name = "flux_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public Flux getValue(String arg1, BigDecimal arg2) { return Flux.just(value); } - @CachePut(name = "flux_cache", tags = DummyCacheManager.class) + @CachePut(DummyCache2.class) public String putValue(String arg1, BigDecimal arg2) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableTargetPutFlux.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableWrongFluxPut.java similarity index 70% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableTargetPutFlux.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableWrongFluxPut.java index 397a46932..8157fd6ab 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableTargetPutFlux.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/flux/CacheableWrongFluxPut.java @@ -3,20 +3,20 @@ import reactor.core.publisher.Flux; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetPutFlux { +public class CacheableWrongFluxPut { public String value = "1"; - @Cacheable(name = "flux_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "flux_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) public Flux putValue(String arg1, BigDecimal arg2) { return Flux.just(value); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMono.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMono.java similarity index 67% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMono.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMono.java index 099c291a7..d57a8728c 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMono.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMono.java @@ -4,30 +4,30 @@ import ru.tinkoff.kora.cache.annotation.CacheInvalidate; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetMono { +public class CacheableMono { public String value = "1"; - @Cacheable(name = "mono_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public Mono getValue(String arg1, BigDecimal arg2) { return Mono.just(value); } - @CachePut(name = "mono_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) public Mono putValue(BigDecimal arg2, String arg3, String arg1) { return Mono.just(value); } - @CacheInvalidate(name = "mono_cache", tags = DummyCacheManager.class) + @CacheInvalidate(DummyCache2.class) public Mono evictValue(String arg1, BigDecimal arg2) { return Mono.empty(); } - @CacheInvalidate(name = "mono_cache", tags = DummyCacheManager.class, invalidateAll = true) + @CacheInvalidate(value = DummyCache2.class, invalidateAll = true) public Mono evictAll() { return Mono.empty(); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoMany.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoMany.java similarity index 51% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoMany.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoMany.java index b6b973ceb..133d5b104 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoMany.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoMany.java @@ -4,34 +4,35 @@ import ru.tinkoff.kora.cache.annotation.CacheInvalidate; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache22; import java.math.BigDecimal; -public class CacheableTargetMonoMany { +public class CacheableMonoMany { public String value = "1"; - @Cacheable(name = "mono_cache", tags = DummyCacheManager.class) - @Cacheable(name = "mono_cache_2", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) + @Cacheable(DummyCache22.class) public Mono getValue(String arg1, BigDecimal arg2) { return Mono.just(value); } - @CachePut(name = "mono_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) - @CachePut(name = "mono_cache_2", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache22.class, parameters = {"arg1", "arg2"}) public Mono putValue(BigDecimal arg2, String arg3, String arg1) { return Mono.just(value); } - @CacheInvalidate(name = "mono_cache", tags = DummyCacheManager.class) - @CacheInvalidate(name = "mono_cache_2", tags = DummyCacheManager.class) + @CacheInvalidate(DummyCache2.class) + @CacheInvalidate(DummyCache22.class) public Mono evictValue(String arg1, BigDecimal arg2) { return Mono.empty(); } - @CacheInvalidate(name = "mono_cache", tags = DummyCacheManager.class, invalidateAll = true) - @CacheInvalidate(name = "mono_cache_2", tags = DummyCacheManager.class, invalidateAll = true) + @CacheInvalidate(value = DummyCache2.class, invalidateAll = true) + @CacheInvalidate(value = DummyCache22.class, invalidateAll = true) public Mono evictAll() { return Mono.empty(); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoOne.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoOne.java new file mode 100644 index 000000000..9d80ae264 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoOne.java @@ -0,0 +1,34 @@ +package ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono; + +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache1; + +import java.math.BigDecimal; + +public class CacheableMonoOne { + + public String value = "1"; + + @Cacheable(DummyCache1.class) + public Mono getValue(String arg1) { + return Mono.just(value); + } + + @CachePut(value = DummyCache1.class, parameters = {"arg1"}) + public Mono putValue(BigDecimal arg2, String arg3, String arg1) { + return Mono.just(value); + } + + @CacheInvalidate(DummyCache1.class) + public Mono evictValue(String arg1) { + return Mono.empty(); + } + + @CacheInvalidate(value = DummyCache1.class, invalidateAll = true) + public Mono evictAll() { + return Mono.empty(); + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetGetMonoVoid.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoWrongGetVoid.java similarity index 73% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetGetMonoVoid.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoWrongGetVoid.java index 36d2ae259..4c542d34e 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetGetMonoVoid.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoWrongGetVoid.java @@ -3,20 +3,20 @@ import reactor.core.publisher.Mono; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetGetMonoVoid { +public class CacheableMonoWrongGetVoid { public String value = "1"; - @Cacheable(name = "mono_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public Mono getValue(String arg1, BigDecimal arg2) { return Mono.empty(); } - @CachePut(name = "mono_cache", tags = DummyCacheManager.class) + @CachePut(DummyCache2.class) public String putValue(String arg1, BigDecimal arg2) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetPutMonoVoid.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoWrongPutVoid.java similarity index 70% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetPutMonoVoid.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoWrongPutVoid.java index e01382f5e..75f912ff0 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetPutMonoVoid.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableMonoWrongPutVoid.java @@ -3,20 +3,20 @@ import reactor.core.publisher.Mono; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetPutMonoVoid { +public class CacheableMonoWrongPutVoid { public String value = "1"; - @Cacheable(name = "mono_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "mono_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) public Mono putValue(String arg1, BigDecimal arg2) { return Mono.empty(); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoManyMany.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoManyMany.java deleted file mode 100644 index 46e1bbba6..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/mono/CacheableTargetMonoManyMany.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor.testdata.reactive.mono; - -import reactor.core.publisher.Mono; -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; - -import java.math.BigDecimal; - -public class CacheableTargetMonoManyMany { - - public String value = "1"; - - @Cacheable(name = "mono_cache", tags = DummyCacheManager.class) - @Cacheable(name = "mono_cache_2", tags = DummyCacheManager.class) - @Cacheable(name = "mono_cache_3", tags = DummyCacheManager.class) - public Mono getValue(String arg1, BigDecimal arg2) { - return Mono.just(value); - } - - @CachePut(name = "mono_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) - @CachePut(name = "mono_cache_2", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) - @CachePut(name = "mono_cache_3", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) - public Mono putValue(BigDecimal arg2, String arg3, String arg1) { - return Mono.just(value); - } - - @CacheInvalidate(name = "mono_cache", tags = DummyCacheManager.class) - @CacheInvalidate(name = "mono_cache_2", tags = DummyCacheManager.class) - @CacheInvalidate(name = "mono_cache_3", tags = DummyCacheManager.class) - public Mono evictValue(String arg1, BigDecimal arg2) { - return Mono.empty(); - } - - @CacheInvalidate(name = "mono_cache", tags = DummyCacheManager.class, invalidateAll = true) - @CacheInvalidate(name = "mono_cache_2", tags = DummyCacheManager.class, invalidateAll = true) - @CacheInvalidate(name = "mono_cache_3", tags = DummyCacheManager.class, invalidateAll = true) - public Mono evictAll() { - return Mono.empty(); - } -} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableTargetGetPublisher.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableWrongPublisherGet.java similarity index 74% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableTargetGetPublisher.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableWrongPublisherGet.java index 4cdc12171..8afb2d948 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableTargetGetPublisher.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableWrongPublisherGet.java @@ -4,20 +4,20 @@ import reactor.core.publisher.Flux; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetGetPublisher { +public class CacheableWrongPublisherGet { public String value = "1"; - @Cacheable(name = "publisher_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public Publisher getValue(String arg1, BigDecimal arg2) { return Flux.just(value); } - @CachePut(name = "publisher_cache", tags = DummyCacheManager.class) + @CachePut(DummyCache2.class) public String putValue(String arg1, BigDecimal arg2) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableTargetPutPublisher.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableWrongPublisherPut.java similarity index 71% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableTargetPutPublisher.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableWrongPublisherPut.java index d1e595fab..ec41eab91 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableTargetPutPublisher.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/reactive/publisher/CacheableWrongPublisherPut.java @@ -4,20 +4,20 @@ import reactor.core.publisher.Flux; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetPutPublisher { +public class CacheableWrongPublisherPut { public String value = "1"; - @Cacheable(name = "publisher_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "publisher_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) public Publisher putValue(String arg1, BigDecimal arg2) { return Flux.just(value); } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSync.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSync.java similarity index 62% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSync.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSync.java index 3da81a98c..9e1e746ac 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSync.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSync.java @@ -3,30 +3,30 @@ import ru.tinkoff.kora.cache.annotation.CacheInvalidate; import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetSync { +public class CacheableSync { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "sync_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) public String putValue(BigDecimal arg2, String arg3, String arg1) { return value; } - @CacheInvalidate(name = "sync_cache", tags = DummyCacheManager.class) + @CacheInvalidate(DummyCache2.class) public void evictValue(String arg1, BigDecimal arg2) { } - @CacheInvalidate(name = "sync_cache", tags = DummyCacheManager.class, invalidateAll = true) + @CacheInvalidate(value = DummyCache2.class, invalidateAll = true) public void evictAll() { } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncMany.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncMany.java new file mode 100644 index 000000000..c3979c769 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncMany.java @@ -0,0 +1,38 @@ +package ru.tinkoff.kora.cache.annotation.processor.testdata.sync; + +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache22; + +import java.math.BigDecimal; + +public class CacheableSyncMany { + + public String value = "1"; + + @Cacheable(DummyCache2.class) + @Cacheable(DummyCache22.class) + public String getValue(String arg1, BigDecimal arg2) { + return value; + } + + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache22.class, parameters = {"arg1", "arg2"}) + public String putValue(BigDecimal arg2, String arg3, String arg1) { + return value; + } + + @CacheInvalidate(DummyCache2.class) + @CacheInvalidate(DummyCache22.class) + public void evictValue(String arg1, BigDecimal arg2) { + + } + + @CacheInvalidate(value = DummyCache2.class, invalidateAll = true) + @CacheInvalidate(value = DummyCache22.class, invalidateAll = true) + public void evictAll() { + + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncOne.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncOne.java new file mode 100644 index 000000000..639b27751 --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncOne.java @@ -0,0 +1,33 @@ +package ru.tinkoff.kora.cache.annotation.processor.testdata.sync; + +import ru.tinkoff.kora.cache.annotation.CacheInvalidate; +import ru.tinkoff.kora.cache.annotation.CachePut; +import ru.tinkoff.kora.cache.annotation.Cacheable; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache1; + +import java.math.BigDecimal; + +public class CacheableSyncOne { + + public String value = "1"; + + @Cacheable(DummyCache1.class) + public String getValue(String arg1) { + return value; + } + + @CachePut(value = DummyCache1.class, parameters = {"arg1"}) + public String putValue(BigDecimal arg2, String arg3, String arg1) { + return value; + } + + @CacheInvalidate(DummyCache1.class) + public void evictValue(String arg1) { + + } + + @CacheInvalidate(value = DummyCache1.class, invalidateAll = true) + public void evictAll() { + + } +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetAnnotationMultiple.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongAnnotationMany.java similarity index 65% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetAnnotationMultiple.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongAnnotationMany.java index 5be29e501..f73a7b28f 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetAnnotationMultiple.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongAnnotationMany.java @@ -2,16 +2,16 @@ import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetAnnotationMultiple { +public class CacheableSyncWrongAnnotationMany { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) - @CachePut(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) + @CachePut(DummyCache2.class) public String putValue(String arg1, BigDecimal arg2) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentMissing.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentMissing.java similarity index 68% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentMissing.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentMissing.java index 2ef4648bc..49166a295 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentMissing.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentMissing.java @@ -2,20 +2,20 @@ import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetArgumentMissing { +public class CacheableSyncWrongArgumentMissing { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "sync_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg4"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg4"}) public String putValue(BigDecimal arg2, String arg3, String arg1) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentWrongType.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentOrder.java similarity index 67% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentWrongType.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentOrder.java index 3bc607fe7..67812a562 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentWrongType.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentOrder.java @@ -2,20 +2,20 @@ import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetArgumentWrongType { +public class CacheableSyncWrongArgumentOrder { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "sync_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg3"}) + @CachePut(value = DummyCache2.class, parameters = {"arg2", "arg1"}) public String putValue(BigDecimal arg2, String arg3, String arg1) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentWrongOrder.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentType.java similarity index 67% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentWrongOrder.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentType.java index 6a33dcb13..f17e1e74d 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetArgumentWrongOrder.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongArgumentType.java @@ -2,20 +2,20 @@ import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetArgumentWrongOrder { +public class CacheableSyncWrongArgumentType { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "sync_cache", tags = DummyCacheManager.class, parameters = {"arg2", "arg1"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg3"}) public String putValue(BigDecimal arg2, String arg3, String arg1) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetGetVoid.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongGetVoid.java similarity index 70% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetGetVoid.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongGetVoid.java index 2d4a89506..fc29ebb74 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetGetVoid.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongGetVoid.java @@ -2,20 +2,20 @@ import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetGetVoid { +public class CacheableSyncWrongGetVoid { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public void getValue(String arg1, BigDecimal arg2) { } - @CachePut(name = "sync_cache", tags = DummyCacheManager.class) + @CachePut(DummyCache2.class) public String putValue(String arg1, BigDecimal arg2) { return value; } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongName.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongName.java new file mode 100644 index 000000000..689fbda3b --- /dev/null +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongName.java @@ -0,0 +1,9 @@ +package ru.tinkoff.kora.cache.annotation.processor.testdata.sync; + +import ru.tinkoff.kora.cache.annotation.Cache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCache; + +@Cache("_1dummy") +public interface CacheableSyncWrongName extends CaffeineCache { + +} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetPutVoid.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongPutVoid.java similarity index 67% rename from cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetPutVoid.java rename to cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongPutVoid.java index a1c61d135..ed886c21f 100644 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetPutVoid.java +++ b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableSyncWrongPutVoid.java @@ -2,20 +2,20 @@ import ru.tinkoff.kora.cache.annotation.CachePut; import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; +import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCache2; import java.math.BigDecimal; -public class CacheableTargetPutVoid { +public class CacheableSyncWrongPutVoid { public String value = "1"; - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) + @Cacheable(DummyCache2.class) public String getValue(String arg1, BigDecimal arg2) { return value; } - @CachePut(name = "sync_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) + @CachePut(value = DummyCache2.class, parameters = {"arg1", "arg2"}) public void putValue(String arg1, BigDecimal arg2) { } diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetNameInvalid.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetNameInvalid.java deleted file mode 100644 index 1e88534f0..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetNameInvalid.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor.testdata.sync; - -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; - -import java.math.BigDecimal; - -public class CacheableTargetNameInvalid { - - public String value = "1"; - - @CachePut(name = "my-cache", tags = DummyCacheManager.class) - public String putValue(String arg1, BigDecimal arg2) { - return value; - } -} diff --git a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSyncMany.java b/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSyncMany.java deleted file mode 100644 index 7f05f25ac..000000000 --- a/cache/cache-annotation-processor/src/test/java/ru/tinkoff/kora/cache/annotation/processor/testdata/sync/CacheableTargetSyncMany.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.tinkoff.kora.cache.annotation.processor.testdata.sync; - -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.annotation.processor.testcache.DummyCacheManager; - -import java.math.BigDecimal; - -public class CacheableTargetSyncMany { - - public String value = "1"; - - @Cacheable(name = "sync_cache", tags = DummyCacheManager.class) - @Cacheable(name = "sync_cache_2") - public String getValue(String arg1, BigDecimal arg2) { - return value; - } - - @CachePut(name = "sync_cache", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) - @CachePut(name = "sync_cache_2", tags = DummyCacheManager.class, parameters = {"arg1", "arg2"}) - public String putValue(BigDecimal arg2, String arg3, String arg1) { - return value; - } - - @CacheInvalidate(name = "sync_cache", tags = DummyCacheManager.class) - @CacheInvalidate(name = "sync_cache_2", tags = DummyCacheManager.class) - public void evictValue(String arg1, BigDecimal arg2) { - - } - - @CacheInvalidate(name = "sync_cache", tags = DummyCacheManager.class, invalidateAll = true) - @CacheInvalidate(name = "sync_cache_2", tags = DummyCacheManager.class, invalidateAll = true) - public void evictAll() { - - } -} diff --git a/cache/cache-caffeine/build.gradle b/cache/cache-caffeine/build.gradle index da1c52904..654f498ff 100644 --- a/cache/cache-caffeine/build.gradle +++ b/cache/cache-caffeine/build.gradle @@ -1,10 +1,14 @@ dependencies { + compileOnly libs.jetbrains.annotations + compileOnly libs.prometheus.collector.caffeine + api project(":cache:cache-common") annotationProcessor project(':config:config-annotation-processor') implementation project(":config:config-common") implementation libs.caffeine + testImplementation libs.prometheus.collector.caffeine testImplementation testFixtures(project(":annotation-processor-common")) testImplementation project(":annotation-processor-common") testImplementation project(":aop:aop-annotation-processor") diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/AbstractCaffeineCache.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/AbstractCaffeineCache.java new file mode 100644 index 000000000..efd88477c --- /dev/null +++ b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/AbstractCaffeineCache.java @@ -0,0 +1,196 @@ +package ru.tinkoff.kora.cache.caffeine; + +import org.jetbrains.annotations.ApiStatus.Internal; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +@Internal +public abstract class AbstractCaffeineCache implements CaffeineCache { + + private final String name; + private final com.github.benmanes.caffeine.cache.Cache caffeine; + private final CaffeineCacheTelemetry telemetry; + + protected AbstractCaffeineCache(String name, + CaffeineCacheConfig config, + CaffeineCacheFactory factory, + CaffeineCacheTelemetry telemetry) { + this.name = name; + this.caffeine = factory.build(name, config); + this.telemetry = telemetry; + } + + @Override + public V get(@Nonnull K key) { + if (key == null) { + return null; + } + + var telemetryContext = telemetry.create("GET", name); + var value = caffeine.getIfPresent(key); + telemetryContext.recordSuccess(value); + return value; + } + + @Nonnull + @Override + public Map get(@Nonnull Collection keys) { + if (keys == null || keys.isEmpty()) { + return Collections.emptyMap(); + } + + var telemetryContext = telemetry.create("GET_MANY", name); + var values = caffeine.getAllPresent(keys); + telemetryContext.recordSuccess(); + return values; + } + + @Override + public V computeIfAbsent(@Nonnull K key, @Nonnull Function mappingFunction) { + if (key == null) { + return null; + } + + var telemetryContext = telemetry.create("PUT_IF_ABSENT", name); + var value = caffeine.get(key, mappingFunction); + telemetryContext.recordSuccess(); + return value; + } + + @SuppressWarnings("unchecked") + @Nonnull + @Override + public Map computeIfAbsent(@Nonnull Collection keys, @Nonnull Function, Map> mappingFunction) { + if (keys == null || keys.isEmpty()) { + return Collections.emptyMap(); + } + + var telemetryContext = telemetry.create("PUT_IF_ABSENT_MANY", name); + var value = caffeine.getAll(keys, ks -> mappingFunction.apply((Set) ks)); + telemetryContext.recordSuccess(); + return value; + } + + @Nonnull + public V put(@Nonnull K key, @Nonnull V value) { + if (key == null || value == null) { + return value; + } + + var telemetryContext = telemetry.create("PUT", name); + caffeine.put(key, value); + telemetryContext.recordSuccess(); + return value; + } + + @Override + public void invalidate(@Nonnull K key) { + if (key != null) { + var telemetryContext = telemetry.create("INVALIDATE", name); + caffeine.invalidate(key); + telemetryContext.recordSuccess(); + } + } + + @Override + public void invalidate(@Nonnull Collection keys) { + if (keys != null && !keys.isEmpty()) { + var telemetryContext = telemetry.create("INVALIDATE_MANY", name); + caffeine.invalidateAll(keys); + telemetryContext.recordSuccess(); + } + } + + @Override + public void invalidateAll() { + var telemetryContext = telemetry.create("INVALIDATE_ALL", name); + caffeine.invalidateAll(); + telemetryContext.recordSuccess(); + } + + @Nonnull + @Override + public Mono getAsync(@Nonnull K key) { + return (key == null) + ? Mono.empty() + : Mono.fromCallable(() -> get(key)); + } + + @Nonnull + @Override + public Mono> getAsync(@Nonnull Collection keys) { + return (keys == null || keys.isEmpty()) + ? Mono.just(Collections.emptyMap()) + : Mono.fromCallable(() -> get(keys)); + } + + @Nonnull + @Override + public Mono putAsync(@Nonnull K key, @Nonnull V value) { + if (key == null) { + return Mono.justOrEmpty(value); + } + + return Mono.fromCallable(() -> put(key, value)); + } + + @Override + public Mono computeIfAbsentAsync(@Nonnull K key, @Nonnull Function> mappingFunction) { + if (key == null) { + return Mono.empty(); + } + + return Mono.fromCallable(() -> computeIfAbsent(key, (k) -> mappingFunction.apply(k).block(Duration.ofMinutes(5)))); + } + + @Nonnull + @Override + public Mono> computeIfAbsentAsync(@Nonnull Collection keys, @Nonnull Function, Mono>> mappingFunction) { + if (keys == null || keys.isEmpty()) { + return Mono.empty(); + } + + return Mono.fromCallable(() -> computeIfAbsent(keys, (k) -> mappingFunction.apply(k).block(Duration.ofMinutes(5)))); + } + + @Nonnull + @Override + public Mono invalidateAsync(@Nonnull K key) { + if (key == null) { + return Mono.just(false); + } + + return Mono.fromCallable(() -> { + invalidate(key); + return true; + }); + } + + @Override + public Mono invalidateAsync(@Nonnull Collection keys) { + if (keys == null || keys.isEmpty()) { + return Mono.just(false); + } + + return Mono.fromCallable(() -> { + invalidate(keys); + return true; + }); + } + + @Nonnull + @Override + public Mono invalidateAllAsync() { + return Mono.fromCallable(() -> { + invalidateAll(); + return true; + }); + } +} diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCache.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCache.java index d7992bd22..83badc45b 100644 --- a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCache.java +++ b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCache.java @@ -1,104 +1,7 @@ package ru.tinkoff.kora.cache.caffeine; -import com.github.benmanes.caffeine.cache.Cache; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Mono; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; +import ru.tinkoff.kora.cache.Cache; -import javax.annotation.Nonnull; +public interface CaffeineCache extends Cache { -final class CaffeineCache implements ru.tinkoff.kora.cache.Cache { - - private static final Logger logger = LoggerFactory.getLogger(CaffeineCache.class); - - private final String name; - private final Cache caffeine; - private final CacheTelemetry telemetry; - - CaffeineCache(String name, Cache caffeine, CacheTelemetry telemetry) { - this.name = name; - this.caffeine = caffeine; - this.telemetry = telemetry; - } - - @Nonnull - String origin() { - return "caffeine"; - } - - @Override - public V get(@Nonnull K key) { - logger.trace("Cache '{}' looking for value for key: {}", name, key); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.GET, name, origin()); - telemetryContext.startRecording(); - - final V v = caffeine.getIfPresent(key); - if (v == null) { - logger.trace("Cache '{}' no value found for key: {}", name, key); - } else { - logger.debug("Cache '{}' found value for key: {}", name, key); - } - - telemetryContext.recordSuccess(v); - return v; - } - - @Nonnull - public V put(@Nonnull K key, @Nonnull V value) { - logger.trace("Cache '{}' storing for key: {}", name, key); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.PUT, name, origin()); - telemetryContext.startRecording(); - - caffeine.put(key, value); - - telemetryContext.recordSuccess(); - return value; - } - - @Override - public void invalidate(@Nonnull K key) { - logger.trace("Cache '{}' invalidating for key: {}", name, key); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.INVALIDATE, name, origin()); - telemetryContext.startRecording(); - - caffeine.invalidate(key); - - telemetryContext.recordSuccess(); - } - - @Override - public void invalidateAll() { - logger.trace("Cache '{}' invalidating all values", name); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.INVALIDATE_ALL, name, origin()); - telemetryContext.startRecording(); - - caffeine.invalidateAll(); - - telemetryContext.recordSuccess(); - } - - @Nonnull - @Override - public Mono getAsync(@Nonnull K key) { - return Mono.fromCallable(() -> get(key)); - } - - @Nonnull - @Override - public Mono putAsync(@Nonnull K key, @Nonnull V value) { - return Mono.fromCallable(() -> put(key, value)); - } - - @Nonnull - @Override - public Mono invalidateAsync(@Nonnull K key) { - return Mono.fromRunnable(() -> invalidate(key)); - } - - @Nonnull - @Override - public Mono invalidateAllAsync() { - return Mono.fromRunnable(this::invalidateAll); - } } diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheConfig.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheConfig.java index 1ee4d5af6..e01ae4365 100644 --- a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheConfig.java +++ b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheConfig.java @@ -3,42 +3,22 @@ import ru.tinkoff.kora.config.common.annotation.ConfigValueExtractor; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.time.Duration; -import java.util.Map; @ConfigValueExtractor public interface CaffeineCacheConfig { - String DEFAULT = "default"; - NamedCacheConfig DEFAULT_CONFIG = new NamedCacheConfig(null, null, 100_000L, null); - default Map caffeine() { - return Map.of(); - } - - @Nonnull - default NamedCacheConfig getByName(@Nonnull String name) { - final NamedCacheConfig defaultConfig = this.caffeine().getOrDefault(DEFAULT, DEFAULT_CONFIG); - final NamedCacheConfig namedConfig = this.caffeine().get(name); - if (namedConfig == null) { - return defaultConfig; - } - - return new NamedCacheConfig( - namedConfig.expireAfterWrite == null ? defaultConfig.expireAfterWrite : namedConfig.expireAfterWrite, - namedConfig.expireAfterAccess == null ? defaultConfig.expireAfterAccess : namedConfig.expireAfterAccess, - namedConfig.maximumSize == null ? defaultConfig.maximumSize : namedConfig.maximumSize, - namedConfig.initialSize == null ? defaultConfig.initialSize : namedConfig.initialSize - ); - } + @Nullable + Duration expireAfterWrite(); - @ConfigValueExtractor - record NamedCacheConfig( - @Nullable Duration expireAfterWrite, - @Nullable Duration expireAfterAccess, - @Nullable Long maximumSize, - @Nullable Integer initialSize) { + @Nullable + Duration expireAfterAccess(); + default Long maximumSize() { + return 100_000L; } + + @Nullable + Integer initialSize(); } diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheFactory.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheFactory.java index e43056768..c757fe846 100644 --- a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheFactory.java +++ b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheFactory.java @@ -1,24 +1,11 @@ package ru.tinkoff.kora.cache.caffeine; import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.stats.StatsCounter; import javax.annotation.Nonnull; -public final class CaffeineCacheFactory { +public interface CaffeineCacheFactory { @Nonnull - public Cache build(@Nonnull CaffeineCacheConfig.NamedCacheConfig config) { - final Caffeine builder = (Caffeine) Caffeine.newBuilder(); - if (config.expireAfterWrite() != null) - builder.expireAfterWrite(config.expireAfterWrite()); - if (config.expireAfterAccess() != null) - builder.expireAfterAccess(config.expireAfterAccess()); - if (config.initialSize() != null) - builder.initialCapacity(config.initialSize()); - if (config.maximumSize() != null) - builder.maximumSize(config.maximumSize()); - return builder.recordStats(StatsCounter::disabledStatsCounter).build(); - } + Cache build(@Nonnull String name, @Nonnull CaffeineCacheConfig config); } diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheManager.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheManager.java deleted file mode 100644 index 167090a04..000000000 --- a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheManager.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import ru.tinkoff.kora.cache.CacheManager; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; - -import javax.annotation.Nonnull; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public final class CaffeineCacheManager implements CacheManager { - - private static final Logger logger = LoggerFactory.getLogger(CaffeineCacheManager.class); - - private final CaffeineCacheFactory factory; - private final CaffeineCacheConfig config; - private final CacheTelemetry telemetry; - - private final Map> cacheMap = new ConcurrentHashMap<>(); - - CaffeineCacheManager(CaffeineCacheFactory factory, CaffeineCacheConfig config, CacheTelemetry telemetry) { - this.factory = factory; - this.config = config; - this.telemetry = telemetry; - } - - @Nonnull - @Override - public ru.tinkoff.kora.cache.Cache getCache(@Nonnull String name) { - return cacheMap.computeIfAbsent(name, k -> { - logger.trace("Build cache for name: {}", name); - var namedConfig = config.getByName(name); - return new CaffeineCache<>(name, factory.build(namedConfig), telemetry); - }); - } -} diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheModule.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheModule.java index 2da440c69..9365a06c1 100644 --- a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheModule.java +++ b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheModule.java @@ -1,39 +1,49 @@ package ru.tinkoff.kora.cache.caffeine; -import ru.tinkoff.kora.application.graph.TypeRef; -import ru.tinkoff.kora.cache.CacheManager; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import io.prometheus.client.cache.caffeine.CacheMetricsCollector; import ru.tinkoff.kora.cache.telemetry.CacheMetrics; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; -import ru.tinkoff.kora.cache.telemetry.DefaultCacheTelemetry; +import ru.tinkoff.kora.cache.telemetry.CacheTracer; import ru.tinkoff.kora.common.DefaultComponent; -import ru.tinkoff.kora.common.Tag; import ru.tinkoff.kora.config.common.Config; import ru.tinkoff.kora.config.common.extractor.ConfigValueExtractor; +import javax.annotation.Nonnull; import javax.annotation.Nullable; public interface CaffeineCacheModule { - @Tag(CaffeineCacheManager.class) @DefaultComponent - default CacheTelemetry defaultCacheTelemetry(@Nullable CacheMetrics metrics) { - return new DefaultCacheTelemetry(metrics, null); + default CaffeineCacheTelemetry caffeineCacheTelemetry(@Nullable CacheMetrics metrics, @Nullable CacheTracer tracer) { + return new CaffeineCacheTelemetry(metrics, tracer); } - default CaffeineCacheConfig caffeineCacheConfig(Config config, ConfigValueExtractor extractor) { - return extractor.extract(config.get("cache")); - } - - default CaffeineCacheFactory caffeineCacheFactory() { - return new CaffeineCacheFactory(); - } + @DefaultComponent + default CaffeineCacheFactory caffeineCacheFactory(@Nullable CacheMetricsCollector cacheMetricsCollector) { + return new CaffeineCacheFactory() { + @Nonnull + @Override + public Cache build(@Nonnull String name, @Nonnull CaffeineCacheConfig config) { + final Caffeine builder = (Caffeine) Caffeine.newBuilder(); + if (config.expireAfterWrite() != null) + builder.expireAfterWrite(config.expireAfterWrite()); + if (config.expireAfterAccess() != null) + builder.expireAfterAccess(config.expireAfterAccess()); + if (config.initialSize() != null) + builder.initialCapacity(config.initialSize()); + if (config.maximumSize() != null) + builder.maximumSize(config.maximumSize()); - @Tag(CaffeineCacheManager.class) - default CacheManager taggedCaffeineCacheManager(CaffeineCacheFactory factory, - CaffeineCacheConfig config, - @Tag(CaffeineCacheManager.class) CacheTelemetry telemetry, - TypeRef keyRef, - TypeRef valueRef) { - return new CaffeineCacheManager<>(factory, config, telemetry); + final Cache cache; + if (cacheMetricsCollector != null) { + cache = builder.recordStats().build(); + cacheMetricsCollector.addCache(name, cache); + } else { + cache = builder.build(); + } + return cache; + } + }; } } diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheTelemetry.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheTelemetry.java new file mode 100644 index 000000000..f4019b933 --- /dev/null +++ b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/CaffeineCacheTelemetry.java @@ -0,0 +1,130 @@ +package ru.tinkoff.kora.cache.caffeine; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.tinkoff.kora.cache.telemetry.CacheMetrics; +import ru.tinkoff.kora.cache.telemetry.CacheTelemetryOperation; +import ru.tinkoff.kora.cache.telemetry.CacheTracer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +//TODO caffeine metrics??? +public final class CaffeineCacheTelemetry { + + private static final String ORIGIN = "caffeine"; + + record Operation(@Nonnull String name, @Nonnull String cacheName) implements CacheTelemetryOperation { + @Nonnull + @Override + public String origin() { + return ORIGIN; + } + } + + interface TelemetryContext { + void recordSuccess(); + + void recordSuccess(@Nullable Object valueFromCache); + + void recordFailure(@Nullable Throwable throwable); + } + + private static final Logger logger = LoggerFactory.getLogger(CaffeineCacheTelemetry.class); + + private static final TelemetryContext STUB_CONTEXT = new StubCacheTelemetry(); + + @Nullable + private final CacheMetrics metrics; + @Nullable + private final CacheTracer tracer; + private final boolean isStubTelemetry; + + CaffeineCacheTelemetry(@Nullable CacheMetrics metrics, @Nullable CacheTracer tracer) { + this.metrics = metrics; + this.tracer = tracer; + this.isStubTelemetry = metrics == null && tracer == null; + } + + record StubCacheTelemetry() implements TelemetryContext { + + @Override + public void recordSuccess() {} + + @Override + public void recordSuccess(@Nullable Object valueFromCache) {} + + @Override + public void recordFailure(@Nullable Throwable throwable) {} + } + + class DefaultCacheTelemetryContext implements TelemetryContext { + + private final Operation operation; + + private CacheTracer.CacheSpan span; + private final long startedInNanos = System.nanoTime(); + + DefaultCacheTelemetryContext(Operation operation) { + logger.trace("Operation '{}' for cache '{}' started", operation.name(), operation.cacheName()); + if (tracer != null) { + span = tracer.trace(operation); + } + this.operation = operation; + } + + @Override + public void recordSuccess() { + recordSuccess(null); + } + + @Override + public void recordSuccess(@Nullable Object valueFromCache) { + if (metrics != null) { + final long durationInNanos = System.nanoTime() - startedInNanos; + metrics.recordSuccess(operation, durationInNanos, valueFromCache); + } + if (span != null) { + span.recordSuccess(); + } + + if (operation.name().startsWith("GET")) { + if (valueFromCache == null) { + logger.trace("Operation '{}' for cache '{}' didn't retried value", operation.name(), operation.cacheName()); + } else { + logger.debug("Operation '{}' for cache '{}' retried value", operation.name(), operation.cacheName()); + } + } else { + logger.trace("Operation '{}' for cache '{}' completed", operation.name(), operation.cacheName()); + } + } + + @Override + public void recordFailure(@Nullable Throwable throwable) { + if (metrics != null) { + final long durationInNanos = System.nanoTime() - startedInNanos; + metrics.recordFailure(operation, durationInNanos, throwable); + } + if (span != null) { + span.recordFailure(throwable); + } + + if (throwable != null) { + logger.warn("Operation '{}' failed for cache '{}' with message: {}", + operation.name(), operation.cacheName(), throwable.getMessage()); + } else { + logger.warn("Operation '{}' failed for cache '{}'", + operation.name(), operation.cacheName()); + } + } + } + + @Nonnull + TelemetryContext create(@Nonnull String operationName, @Nonnull String cacheName) { + if (isStubTelemetry) { + return STUB_CONTEXT; + } + + return new DefaultCacheTelemetryContext(new Operation(operationName, cacheName)); + } +} diff --git a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/DefaultCaffeineCacheModule.java b/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/DefaultCaffeineCacheModule.java deleted file mode 100644 index 3b7989eb9..000000000 --- a/cache/cache-caffeine/src/main/java/ru/tinkoff/kora/cache/caffeine/DefaultCaffeineCacheModule.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine; - -import ru.tinkoff.kora.application.graph.TypeRef; -import ru.tinkoff.kora.cache.CacheManager; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; -import ru.tinkoff.kora.common.Tag; - -public interface DefaultCaffeineCacheModule extends CaffeineCacheModule { - - default CacheManager defaultCaffeineCacheManager(CaffeineCacheFactory factory, - CaffeineCacheConfig config, - @Tag(CaffeineCacheManager.class) CacheTelemetry telemetry, - TypeRef keyRef, - TypeRef valueRef) { - return taggedCaffeineCacheManager(factory, config, telemetry, keyRef, valueRef); - } -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/CacheRunner.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/CacheRunner.java index 024397ce6..6d80c3d48 100644 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/CacheRunner.java +++ b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/CacheRunner.java @@ -1,44 +1,40 @@ package ru.tinkoff.kora.cache.caffeine; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Assertions; -import ru.tinkoff.kora.annotation.processor.common.TestUtils; -import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; -import ru.tinkoff.kora.application.graph.ApplicationGraphDraw; -import ru.tinkoff.kora.cache.annotation.processor.CacheKeyAnnotationProcessor; -import ru.tinkoff.kora.cache.caffeine.testdata.AppWithConfig; -import ru.tinkoff.kora.cache.caffeine.testdata.CacheableTargetMono; -import ru.tinkoff.kora.cache.caffeine.testdata.CacheableTargetSync; -import ru.tinkoff.kora.config.annotation.processor.processor.ConfigSourceAnnotationProcessor; -import ru.tinkoff.kora.kora.app.annotation.processor.KoraAppProcessor; +import ru.tinkoff.kora.cache.caffeine.testdata.DummyCache; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; +import java.time.Duration; -abstract class CacheRunner extends Assertions { +abstract class CacheRunner extends Assertions implements CaffeineCacheModule { - ApplicationGraphDraw createGraphDraw() { - try { - return createGraphDraw(AppWithConfig.class, CacheableTargetSync.class, CacheableTargetMono.class); - } catch (Exception e) { - throw new IllegalStateException(e); - } + public static CaffeineCacheConfig getConfig() { + return new CaffeineCacheConfig() { + @Nullable + @Override + public Duration expireAfterWrite() { + return null; + } + + @Nullable + @Override + public Duration expireAfterAccess() { + return null; + } + + @Nullable + @Override + public Integer initialSize() { + return null; + } + }; } - ApplicationGraphDraw createGraphDraw(Class app, Class... targetClasses) throws Exception { + protected DummyCache createCache() { try { - final List> classes = new ArrayList<>(List.of(targetClasses)); - classes.add(app); - var classLoader = TestUtils.annotationProcess(classes, new KoraAppProcessor(), new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor(), new ConfigSourceAnnotationProcessor()); - var clazz = classLoader.loadClass(app.getName() + "Graph"); - var constructors = (Constructor>[]) clazz.getConstructors(); - return constructors[0].newInstance().get(); + return new DummyCache(getConfig(), caffeineCacheFactory(null), caffeineCacheTelemetry(null, null)); } catch (Exception e) { - if (e.getCause() != null) { - throw (Exception) e.getCause(); - } - throw e; + throw new IllegalStateException(e); } } } diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheAopTests.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheAopTests.java deleted file mode 100644 index d48f35a24..000000000 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheAopTests.java +++ /dev/null @@ -1,159 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import ru.tinkoff.kora.cache.caffeine.testdata.CacheableMockLifecycle; -import ru.tinkoff.kora.cache.caffeine.testdata.CacheableTargetMono; - -import java.math.BigDecimal; -import java.time.Duration; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class MonoCacheAopTests extends CacheRunner { - - private CacheableTargetMono service = null; - - private CacheableTargetMono getService() { - if (service != null) { - return service; - } - - var graphDraw = createGraphDraw(); - var graph = graphDraw.init().block(); - var values = graphDraw.getNodes() - .stream() - .map(graph::get) - .toList(); - - service = values.stream() - .filter(a -> a instanceof CacheableMockLifecycle) - .map(a -> ((CacheableMockLifecycle) a).mono()) - .findFirst().orElseThrow(); - return service; - } - - @BeforeEach - void reset() { - if (service != null) { - service.evictAll().block(Duration.ofSeconds(1)); - } - } - - @Test - void getFromCacheWhenWasCacheEmpty() { - // given - final CacheableTargetMono service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String notCached = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - service.value = "2"; - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(notCached, fromCache); - assertNotEquals("2", fromCache); - } - - @Test - void getFromCacheWhenCacheFilled() { - // given - final CacheableTargetMono service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(cached, fromCache); - } - - @Test - void getFromCacheWrongKeyWhenCacheFilled() { - // given - final CacheableTargetMono service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - - // then - final String fromCache = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - assertEquals(service.value, fromCache); - } - - @Test - void getFromCacheWhenCacheFilledOtherKey() { - // given - final CacheableTargetMono service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - service.value = "2"; - final String initial = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, initial); - - // then - final String fromCache = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - assertEquals(initial, fromCache); - } - - @Test - void getFromCacheWhenCacheInvalidate() { - // given - final CacheableTargetMono service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - service.evictValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - } - - @Test - void getFromCacheWhenCacheInvalidateAll() { - // given - final CacheableTargetMono service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.value = "2"; - service.evictAll().block(Duration.ofMinutes(1)); - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - } -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheTests.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheTests.java new file mode 100644 index 000000000..c8b35e98d --- /dev/null +++ b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/MonoCacheTests.java @@ -0,0 +1,84 @@ +package ru.tinkoff.kora.cache.caffeine; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.cache.caffeine.testdata.DummyCache; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MonoCacheTests extends CacheRunner { + + private final DummyCache cache = createCache(); + + @BeforeEach + void reset() { + cache.invalidateAllAsync().block(); + } + + @Test + void getWhenCacheEmpty() { + // given + var key = "1"; + + // when + assertNull(cache.getAsync(key).block()); + } + + @Test + void getWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.putAsync(key, value).block(); + + // then + final String fromCache = cache.getAsync(key).block(); + assertEquals(value, fromCache); + } + + @Test + void getWrongKeyWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.putAsync(key, value).block(); + + // then + final String fromCache = cache.getAsync("2").block(); + assertNull(fromCache); + } + + @Test + void getWhenCacheInvalidate() { + // given + var key = "1"; + var value = "1"; + cache.putAsync(key, value).block(); + + // when + cache.invalidateAsync(key).block(); + + // then + final String fromCache = cache.getAsync(key).block(); + assertNull(fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidateAll() { + // given + var key = "1"; + var value = "1"; + cache.putAsync(key, value).block(); + + // when + cache.invalidateAllAsync().block(); + + // then + final String fromCache = cache.getAsync(key).block(); + assertNull(fromCache); + } +} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheAopTests.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheAopTests.java deleted file mode 100644 index 6c7828800..000000000 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheAopTests.java +++ /dev/null @@ -1,158 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import ru.tinkoff.kora.cache.caffeine.testdata.CacheableMockLifecycle; -import ru.tinkoff.kora.cache.caffeine.testdata.CacheableTargetSync; - -import java.math.BigDecimal; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class SyncCacheAopTests extends CacheRunner { - - private CacheableTargetSync service = null; - - private CacheableTargetSync getService() { - if (service != null) { - return service; - } - - var graphDraw = createGraphDraw(); - var graph = graphDraw.init().block(); - var values = graphDraw.getNodes() - .stream() - .map(graph::get) - .toList(); - - service = values.stream() - .filter(a -> a instanceof CacheableMockLifecycle) - .map(a -> ((CacheableMockLifecycle) a).sync()) - .findFirst().orElseThrow(); - return service; - } - - @BeforeEach - void reset() { - if (service != null) { - service.evictAll(); - } - } - - @Test - void getFromCacheWhenWasCacheEmpty() { - // given - final CacheableTargetSync service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String notCached = service.getValue("1", BigDecimal.ZERO); - service.value = "2"; - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO); - assertEquals(notCached, fromCache); - assertNotEquals("2", fromCache); - } - - @Test - void getFromCacheWhenCacheFilled() { - // given - final CacheableTargetSync service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.value = "2"; - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO); - assertEquals(cached, fromCache); - } - - @Test - void getFromCacheWrongKeyWhenCacheFilled() { - // given - final CacheableTargetSync service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.value = "2"; - - // then - final String fromCache = service.getValue("2", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - assertEquals(service.value, fromCache); - } - - @Test - void getFromCacheWhenCacheFilledOtherKey() { - // given - final CacheableTargetSync service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); - service.value = "2"; - final String initial = service.getValue("2", BigDecimal.ZERO); - assertNotEquals(cached, initial); - - // then - final String fromCache = service.getValue("2", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - assertEquals(initial, fromCache); - } - - @Test - void getFromCacheWhenCacheInvalidate() { - // given - final CacheableTargetSync service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.value = "2"; - service.evictValue("1", BigDecimal.ZERO); - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - } - - @Test - void getFromCacheWhenCacheInvalidateAll() { - // given - final CacheableTargetSync service = getService(); - - service.value = "1"; - assertNotNull(service); - - // when - final String initial = service.getValue("1", BigDecimal.ZERO); - final String cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.value = "2"; - service.evictAll(); - - // then - final String fromCache = service.getValue("1", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - } -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheTests.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheTests.java new file mode 100644 index 000000000..1c24cc108 --- /dev/null +++ b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/SyncCacheTests.java @@ -0,0 +1,84 @@ +package ru.tinkoff.kora.cache.caffeine; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.cache.caffeine.testdata.DummyCache; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SyncCacheTests extends CacheRunner { + + private final DummyCache cache = createCache(); + + @BeforeEach + void reset() { + cache.invalidateAll(); + } + + @Test + void getWhenCacheEmpty() { + // given + var key = "1"; + + // when + assertNull(cache.get(key)); + } + + @Test + void getWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.put(key, value); + + // then + final String fromCache = cache.get(key); + assertEquals(value, fromCache); + } + + @Test + void getWrongKeyWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.put(key, value); + + // then + final String fromCache = cache.get("2"); + assertNull(fromCache); + } + + @Test + void getWhenCacheInvalidate() { + // given + var key = "1"; + var value = "1"; + cache.put(key, value); + + // when + cache.invalidate(key); + + // then + final String fromCache = cache.get(key); + assertNull(fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidateAll() { + // given + var key = "1"; + var value = "1"; + cache.put(key, value); + + // when + cache.invalidateAll(); + + // then + final String fromCache = cache.get(key); + assertNull(fromCache); + } +} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/AppWithConfig.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/AppWithConfig.java deleted file mode 100644 index b907437c1..000000000 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/AppWithConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine.testdata; - -import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule; -import ru.tinkoff.kora.common.KoraApp; -import ru.tinkoff.kora.common.annotation.Root; -import ru.tinkoff.kora.config.common.Config; -import ru.tinkoff.kora.config.common.DefaultConfigExtractorsModule; -import ru.tinkoff.kora.config.common.factory.MapConfigFactory; - -import java.util.Map; - -@KoraApp -public interface AppWithConfig extends DefaultConfigExtractorsModule, CaffeineCacheModule { - - default Config config() { - return MapConfigFactory.fromMap(Map.of( - "cache", Map.of( - "caffeine", Map.of( - "sync_cache", Map.of("maximumSize", 10), - "mono_cache", Map.of("maximumSize", 10) - ) - ) - )); - } - - @Root - default CacheableMockLifecycle object(CacheableTargetSync cacheableTargetSync, CacheableTargetMono cacheableTargetMono) { - return new CacheableMockLifecycle(cacheableTargetMono, cacheableTargetSync); - } -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableMockLifecycle.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableMockLifecycle.java deleted file mode 100644 index ea4bc52ce..000000000 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableMockLifecycle.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine.testdata; - -public record CacheableMockLifecycle(CacheableTargetMono mono, CacheableTargetSync sync) { - -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetMono.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetMono.java deleted file mode 100644 index acf3c258b..000000000 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetMono.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine.testdata; - -import reactor.core.publisher.Mono; -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.caffeine.CaffeineCacheManager; -import ru.tinkoff.kora.common.Component; - -import java.math.BigDecimal; - -@Component -public class CacheableTargetMono { - - public static final String CACHE_NAME = "mono_cache"; - - public String value = "1"; - - @Cacheable(name = CACHE_NAME, tags = CaffeineCacheManager.class) - public Mono getValue(String arg1, BigDecimal arg2) { - return Mono.just(value); - } - - @CachePut(name = CACHE_NAME, tags = CaffeineCacheManager.class, parameters = {"arg1", "arg2"}) - public Mono putValue(BigDecimal arg2, String arg3, String arg1) { - return Mono.just(value); - } - - @CacheInvalidate(name = CACHE_NAME, tags = CaffeineCacheManager.class) - public Mono evictValue(String arg1, BigDecimal arg2) { - return Mono.empty(); - } - - @CacheInvalidate(name = CACHE_NAME, tags = CaffeineCacheManager.class, invalidateAll = true) - public Mono evictAll() { - return Mono.empty(); - } -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetSync.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetSync.java deleted file mode 100644 index a5a3e7475..000000000 --- a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/CacheableTargetSync.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.tinkoff.kora.cache.caffeine.testdata; - -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.caffeine.CaffeineCacheManager; -import ru.tinkoff.kora.common.Component; - -import java.math.BigDecimal; - -@Component -public class CacheableTargetSync { - - public static final String CACHE_NAME = "sync_cache"; - - public String value = "1"; - - @Cacheable(name = CACHE_NAME, tags = CaffeineCacheManager.class) - public String getValue(String arg1, BigDecimal arg2) { - return value; - } - - @CachePut(name = CACHE_NAME, tags = CaffeineCacheManager.class, parameters = {"arg1", "arg2"}) - public String putValue(BigDecimal arg2, String arg3, String arg1) { - return value; - } - - @CacheInvalidate(name = CACHE_NAME, tags = CaffeineCacheManager.class) - public void evictValue(String arg1, BigDecimal arg2) { - - } - - @CacheInvalidate(name = CACHE_NAME, tags = CaffeineCacheManager.class, invalidateAll = true) - public void evictAll() { - - } -} diff --git a/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/DummyCache.java b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/DummyCache.java new file mode 100644 index 000000000..b56c13c15 --- /dev/null +++ b/cache/cache-caffeine/src/test/java/ru/tinkoff/kora/cache/caffeine/testdata/DummyCache.java @@ -0,0 +1,13 @@ +package ru.tinkoff.kora.cache.caffeine.testdata; + +import ru.tinkoff.kora.cache.caffeine.AbstractCaffeineCache; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheFactory; +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheTelemetry; + +public final class DummyCache extends AbstractCaffeineCache { + + public DummyCache(CaffeineCacheConfig config, CaffeineCacheFactory factory, CaffeineCacheTelemetry telemetry) { + super("dummy", config, factory, telemetry); + } +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/Cache.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/Cache.java index dde96ac33..72deba43b 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/Cache.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/Cache.java @@ -4,12 +4,21 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; /** * Represents base Cache contract. */ public interface Cache { + @Nonnull + default LoadableCache asLoadable(@Nonnull CacheLoader cacheLoader) { + return new LoadableCacheImpl<>(this, cacheLoader); + } + /** * Resolve the given value for the given key. * @@ -19,6 +28,9 @@ public interface Cache { @Nullable V get(@Nonnull K key); + @Nonnull + Map get(@Nonnull Collection keys); + /** * Resolve the given value for the given key. * @@ -28,6 +40,9 @@ public interface Cache { @Nonnull Mono getAsync(@Nonnull K key); + @Nonnull + Mono> getAsync(@Nonnull Collection keys); + /** * Cache the specified value using the specified key. * @@ -37,6 +52,35 @@ public interface Cache { @Nonnull V put(@Nonnull K key, @Nonnull V value); + /** + * @param key to look for value or compute and put if absent + * @param mappingFunction to use for value computing + * @return existing or computed value + */ + V computeIfAbsent(@Nonnull K key, @Nonnull Function mappingFunction); + + /** + * @param keys to look for value or compute and put if absent + * @param mappingFunction to use for value computing + * @return existing or computed value + */ + @Nonnull + Map computeIfAbsent(@Nonnull Collection keys, @Nonnull Function, Map> mappingFunction); + + /** + * Invalidate the value for the given key. + * + * @param key The key to invalid + */ + void invalidate(@Nonnull K key); + + void invalidate(@Nonnull Collection keys); + + /** + * Invalidate all cached values within this cache. + */ + void invalidateAll(); + /** * Cache the specified value using the specified key. * @@ -47,12 +91,21 @@ public interface Cache { @Nonnull Mono putAsync(@Nonnull K key, @Nonnull V value); + /** - * Invalidate the value for the given key. - * - * @param key The key to invalid + * @param key to look for value or compute and put if absent + * @param mappingFunction to use for value computing + * @return existing or computed value */ - void invalidate(@Nonnull K key); + Mono computeIfAbsentAsync(@Nonnull K key, @Nonnull Function> mappingFunction); + + /** + * @param keys to look for value or compute and put if absent + * @param mappingFunction to use for value computing + * @return existing or computed value + */ + @Nonnull + Mono> computeIfAbsentAsync(@Nonnull Collection keys, @Nonnull Function, Mono>> mappingFunction); /** * Invalidate the value for the given key. @@ -60,16 +113,13 @@ public interface Cache { * @param key The key to invalid */ @Nonnull - Mono invalidateAsync(@Nonnull K key); + Mono invalidateAsync(@Nonnull K key); - /** - * Invalidate all cached values within this cache. - */ - void invalidateAll(); + Mono invalidateAsync(@Nonnull Collection keys); /** * Invalidate all cached values within this cache. */ @Nonnull - Mono invalidateAllAsync(); + Mono invalidateAllAsync(); } diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheBuilder.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheBuilder.java new file mode 100644 index 000000000..93bf263e6 --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheBuilder.java @@ -0,0 +1,17 @@ +package ru.tinkoff.kora.cache; + +import javax.annotation.Nonnull; + +public interface CacheBuilder { + + @Nonnull + CacheBuilder addCache(@Nonnull Cache cache); + + @Nonnull + Cache build(); + + @Nonnull + static CacheBuilder builder(@Nonnull Cache cache) { + return new FacadeCacheBuilder<>(cache); + } +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKey.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKey.java index 7f063c4b5..90e909e71 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKey.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKey.java @@ -1,18 +1,315 @@ package ru.tinkoff.kora.cache; import javax.annotation.Nonnull; +import java.util.Arrays; import java.util.List; /** * Represent CacheKey interface that is used by the implementation that represents method arguments as key for Cache * - * @see Object#toString() generates string where all values are separated with '-' according to contract for CacheKey + * @see CacheKey#joined() generates string where all values are separated with '-' according to contract for CacheKey */ public interface CacheKey { + /** + * @return {@link Boolean#TRUE} if all sub keys are nullable + */ + boolean isEmpty(); + + @Nonnull + String joined(); + /** * @return cache key values arguments that are subjected for Cache key */ @Nonnull List values(); + + @Nonnull + static Key2 of(K1 key1, K2 key2) { + return new CacheKeyImpl.CacheKeyImpl2<>(key1, key2); + } + + @Nonnull + static Key3 of(K1 key1, K2 key2, K3 key3) { + return new CacheKeyImpl.CacheKeyImpl3<>(key1, key2, key3); + } + + @Nonnull + static Key4 of(K1 key1, K2 key2, K3 key3, K4 key4) { + return new CacheKeyImpl.CacheKeyImpl4<>(key1, key2, key3, key4); + } + + @Nonnull + static Key5 of(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5) { + return new CacheKeyImpl.CacheKeyImpl5<>(key1, key2, key3, key4, key5); + } + + @Nonnull + static Key6 of(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6) { + return new CacheKeyImpl.CacheKeyImpl6<>(key1, key2, key3, key4, key5, key6); + } + + @Nonnull + static Key7 of(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7) { + return new CacheKeyImpl.CacheKeyImpl7<>(key1, key2, key3, key4, key5, key6, key7); + } + + @Nonnull + static Key8 of(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8) { + return new CacheKeyImpl.CacheKeyImpl8<>(key1, key2, key3, key4, key5, key6, key7, key8); + } + + @Nonnull + static Key9 of(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9) { + return new CacheKeyImpl.CacheKeyImpl9<>(key1, key2, key3, key4, key5, key6, key7, key8, key9); + } + + interface Key2 extends CacheKey { + + K1 key1(); + + K2 key2(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2(); + } + } + + interface Key3 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3(); + } + } + + interface Key4 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + K4 key4(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null && key4() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3(), key4()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3() + "|" + key4(); + } + } + + interface Key5 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + K4 key4(); + + K5 key5(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null && key4() == null && key5() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3(), key4(), key5()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3() + "|" + key4() + "|" + key5(); + } + } + + interface Key6 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + K4 key4(); + + K5 key5(); + + K6 key6(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null && key4() == null && key5() == null && key6() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3(), key4(), key5(), key6()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3() + "|" + key4() + "|" + key5() + "|" + key6(); + } + } + + interface Key7 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + K4 key4(); + + K5 key5(); + + K6 key6(); + + K7 key7(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null && key4() == null && key5() == null && key6() == null && key7() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3(), key4(), key5(), key6(), key7()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3() + "|" + key4() + "|" + key5() + "|" + key6() + "|" + key7(); + } + } + + interface Key8 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + K4 key4(); + + K5 key5(); + + K6 key6(); + + K7 key7(); + + K8 key8(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null && key4() == null && key5() == null && key6() == null && key7() == null && key8() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3(), key4(), key5(), key6(), key7(), key8()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3() + "|" + key4() + "|" + key5() + "|" + key6() + "|" + key7() + "|" + key8(); + } + } + + interface Key9 extends CacheKey { + + K1 key1(); + + K2 key2(); + + K3 key3(); + + K4 key4(); + + K5 key5(); + + K6 key6(); + + K7 key7(); + + K8 key8(); + + K9 key9(); + + @Override + default boolean isEmpty() { + return key1() == null && key2() == null && key3() == null && key4() == null && key5() == null && key6() == null && key7() == null && key8() == null && key9() == null; + } + + @Nonnull + @Override + default List values() { + return Arrays.asList(key1(), key2(), key3(), key4(), key5(), key6(), key7(), key8(), key9()); + } + + @Nonnull + @Override + default String joined() { + return key1() + "|" + key2() + "|" + key3() + "|" + key4() + "|" + key5() + "|" + key6() + "|" + key7() + "|" + key8() + "|" + key9(); + } + } } diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKeyImpl.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKeyImpl.java new file mode 100644 index 000000000..dbd2b67db --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheKeyImpl.java @@ -0,0 +1,71 @@ +package ru.tinkoff.kora.cache; + +class CacheKeyImpl { + + private CacheKeyImpl() {} + + record CacheKeyImpl2(K1 key1, K2 key2) implements CacheKey.Key2 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl3(K1 key1, K2 key2, K3 key3) implements CacheKey.Key3 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl4(K1 key1, K2 key2, K3 key3, K4 key4) implements CacheKey.Key4 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl5(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5) implements CacheKey.Key5 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl6(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6) implements CacheKey.Key6 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl7(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7) implements CacheKey.Key7 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl8(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8) implements CacheKey.Key8 { + + @Override + public String toString() { + return joined(); + } + } + + record CacheKeyImpl9(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, + K9 key9) implements CacheKey.Key9 { + + @Override + public String toString() { + return joined(); + } + } +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoader.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoader.java index 5ea1d1c65..96ba3ed9e 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoader.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoader.java @@ -4,6 +4,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.function.Function; @@ -18,6 +20,15 @@ public interface CacheLoader { @Nullable V load(@Nonnull K key); + /** + * Computes or retrieves the value corresponding to {@code key}. + * + * @param keys to look value for + * @return value associated with key + */ + @Nullable + Map load(@Nonnull Collection keys); + /** * Computes or retrieves the value corresponding to {@code key} asynchronously. * @@ -27,69 +38,72 @@ public interface CacheLoader { @Nonnull Mono loadAsync(@Nonnull K key); - class BlockingCacheLoader implements CacheLoader { - private final ExecutorService executor; - private final Function loader; - - public BlockingCacheLoader(ExecutorService executor, Function loader) { - this.executor = executor; - this.loader = loader; - } - - @Nullable - @Override - public V load(@Nonnull K key) { - return loader.apply(key); - } + /** + * Computes or retrieves the value corresponding to {@code key} asynchronously. + * + * @param keys to look value for + * @return value associated with key or {@link Mono#empty()} + */ + Mono> loadAsync(@Nonnull Collection keys); - @Nonnull - @Override - public Mono loadAsync(@Nonnull K key) { - return Mono.create(sink -> { - executor.submit(() -> { - try { - sink.success(loader.apply(key)); - } catch (Exception e) { - sink.error(e); - } - }); - }); - } + @Nonnull + static CacheLoader blocking(@Nonnull Function loader, + @Nonnull ExecutorService executor) { + return new CacheLoaderImpls.BlockingCacheLoader<>(executor, loader); } - class NonBlockingCacheLoader implements CacheLoader { - private final Function loader; - - public NonBlockingCacheLoader(Function loader) {this.loader = loader;} - - @Nullable - @Override - public V load(@Nonnull K key) { - return loader.apply(key); - } - - @Nonnull - @Override - public Mono loadAsync(@Nonnull K key) { - return Mono.fromCallable(() -> loader.apply(key)); - } + /** + * Create default loadable cache implementation for blocking operation + * + * @param executor Executor to submit load task on async call + * @param loader Blocking load operation + * @param loaderMany Blocking load for multiple keys + * @param Cache key + * @param Cache value + * @return default loadable cache instance + */ + @Nonnull + static CacheLoader blocking(@Nonnull Function loader, + @Nonnull Function, Map> loaderMany, + @Nonnull ExecutorService executor) { + return new CacheLoaderImpls.BlockingCacheLoader<>(executor, loader, loaderMany); } - class AsyncCacheLoader implements CacheLoader { - private final Function> loader; + @Nonnull + static CacheLoader nonBlocking(@Nonnull Function loader) { + return new CacheLoaderImpls.NonBlockingCacheLoader<>(loader); + } - public AsyncCacheLoader(Function> loader) {this.loader = loader;} + /** + * Create default loadable cache implementation for non-blocking operation + * + * @param loader Some long computation + * @param Cache key + * @param Cache value + * @return default loadable cache instance + */ + @Nonnull + static CacheLoader nonBlocking(@Nonnull Function loader, + @Nonnull Function, Map> loaderMany) { + return new CacheLoaderImpls.NonBlockingCacheLoader<>(loader, loaderMany); + } - @Nullable - @Override - public V load(@Nonnull K key) { - return loader.apply(key).block(); - } + /** + * Create default loadable cache implementation for async operation + * + * @param loader Async + * @param Cache key + * @param Cache value + * @return default loadable cache instance + */ + @Nonnull + static CacheLoader async(@Nonnull Function> loader) { + return new CacheLoaderImpls.AsyncCacheLoader<>(loader); + } - @Nonnull - @Override - public Mono loadAsync(@Nonnull K key) { - return loader.apply(key); - } + @Nonnull + static CacheLoader async(@Nonnull Function> loader, + @Nonnull Function, Mono>> loaderMany) { + return new CacheLoaderImpls.AsyncCacheLoader<>(loader, loaderMany); } } diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoaderImpls.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoaderImpls.java new file mode 100644 index 000000000..86497c546 --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheLoaderImpls.java @@ -0,0 +1,171 @@ +package ru.tinkoff.kora.cache; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.function.Function; + +final class CacheLoaderImpls { + + private CacheLoaderImpls() {} + + static final class AsyncCacheLoader implements CacheLoader { + + record Pair(K key, V value) {} + + private final Function> loader; + private final Function, Mono>> loaderMany; + + public AsyncCacheLoader(Function> loader) { + this.loader = loader; + this.loaderMany = (keys) -> { + var valuesMonos = keys.stream() + .map(key -> loader.apply(key) + .map(v -> new Pair<>(key, v))) + .toList(); + + return Flux.merge(valuesMonos) + .collect(() -> new HashMap(), (collector, value) -> collector.put(value.key(), value.value())); + }; + } + + public AsyncCacheLoader(Function> loader, Function, Mono>> loaderMany) { + this.loader = loader; + this.loaderMany = loaderMany; + } + + @Nullable + @Override + public V load(@Nonnull K key) { + return loader.apply(key).block(); + } + + @Nullable + @Override + public Map load(@Nonnull Collection keys) { + return loaderMany.apply(keys).block(); + } + + @Nonnull + @Override + public Mono loadAsync(@Nonnull K key) { + return loader.apply(key); + } + + @Override + public Mono> loadAsync(@Nonnull Collection keys) { + return loaderMany.apply(keys); + } + } + + static final class BlockingCacheLoader implements CacheLoader { + + private final ExecutorService executor; + private final Function loader; + private final Function, Map> loaderMany; + + public BlockingCacheLoader(ExecutorService executor, Function loader) { + this.executor = executor; + this.loader = loader; + this.loaderMany = (keys) -> { + final Map values = new HashMap<>(); + for (K key : keys) { + values.put(key, loader.apply(key)); + } + return values; + }; + } + + public BlockingCacheLoader(ExecutorService executor, Function loader, Function, Map> loaderMany) { + this.executor = executor; + this.loader = loader; + this.loaderMany = loaderMany; + } + + @Nullable + @Override + public V load(@Nonnull K key) { + return loader.apply(key); + } + + @Nullable + @Override + public Map load(@Nonnull Collection keys) { + return loaderMany.apply(keys); + } + + @Nonnull + @Override + public Mono loadAsync(@Nonnull K key) { + return Mono.create(sink -> executor.submit(() -> { + try { + sink.success(loader.apply(key)); + } catch (Exception e) { + sink.error(e); + } + })); + } + + @Override + public Mono> loadAsync(@Nonnull Collection keys) { + return Mono.create(sink -> executor.submit(() -> { + try { + sink.success(loaderMany.apply(keys)); + } catch (Exception e) { + sink.error(e); + } + })); + } + } + + static final class NonBlockingCacheLoader implements CacheLoader { + + private final Function loader; + private final Function, Map> loaderMany; + + public NonBlockingCacheLoader(Function loader) { + this.loader = loader; + this.loaderMany = (keys) -> { + final Map values = new HashMap<>(); + for (K key : keys) { + values.put(key, loader.apply(key)); + } + return values; + }; + } + + public NonBlockingCacheLoader(Function loader, Function, Map> loaderMany) { + this.loader = loader; + this.loaderMany = loaderMany; + } + + @Nullable + @Override + public V load(@Nonnull K key) { + return loader.apply(key); + } + + @Nullable + @Override + public Map load(@Nonnull Collection keys) { + return loaderMany.apply(keys); + } + + @Nonnull + @Override + public Mono loadAsync(@Nonnull K key) { + return Mono.fromCallable(() -> loader.apply(key)); + } + + @Override + public Mono> loadAsync(@Nonnull Collection keys) { + return Mono.fromCallable(() -> loaderMany.apply(keys)); + } + } +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheManager.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheManager.java deleted file mode 100644 index 8e7ebea44..000000000 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/CacheManager.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.tinkoff.kora.cache; - -import javax.annotation.Nonnull; -import java.util.function.Function; - -/** - * Cache manager contract that is responsible for retrieval Cache implementations. - */ -public interface CacheManager { - - /** - * @param name cache name - * @return identified {@link Cache} if it exists or creates new one - */ - Cache getCache(@Nonnull String name); - - interface Builder { - - @Nonnull - Builder addFacadeManager(@Nonnull CacheManager cacheManager); - - @Nonnull - Builder addFacadeFunction(@Nonnull Function> cacheFunction); - - @Nonnull - CacheManager build(); - } - - @Nonnull - static Builder builder() { - return new FacadeCacheManagerBuilder<>(); - } -} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/DefaultLoadableCache.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/DefaultLoadableCache.java deleted file mode 100644 index 97cbc0ddb..000000000 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/DefaultLoadableCache.java +++ /dev/null @@ -1,43 +0,0 @@ -package ru.tinkoff.kora.cache; - -import reactor.core.publisher.Mono; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class DefaultLoadableCache implements LoadableCache { - private final Cache cache; - private final CacheLoader cacheLoader; - - - public DefaultLoadableCache(Cache cache, CacheLoader cacheLoader) { - this.cache = cache; - this.cacheLoader = cacheLoader; - } - - @Nullable - @Override - public V get(@Nonnull K key) { - var fromCache = cache.get(key); - if (fromCache != null) { - return fromCache; - } - - var value = cacheLoader.load(key); - if (value != null) { - cache.put(key, value); - } - - return value; - } - - @Nonnull - @Override - public Mono getAsync(@Nonnull K key) { - return cache.getAsync(key) - .switchIfEmpty( - cacheLoader.loadAsync(key) - .flatMap(value -> cache.putAsync(key, value).thenReturn(value)) - ); - } -} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheBuilder.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheBuilder.java new file mode 100644 index 000000000..6716d785e --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheBuilder.java @@ -0,0 +1,294 @@ +package ru.tinkoff.kora.cache; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +final class FacadeCacheBuilder implements CacheBuilder { + + private final List> facades = new ArrayList<>(); + + FacadeCacheBuilder(@Nonnull Cache cache) { + facades.add(cache); + } + + @Nonnull + @Override + public CacheBuilder addCache(@Nonnull Cache cache) { + facades.add(cache); + return this; + } + + @Nonnull + @Override + public Cache build() { + if (facades.isEmpty()) { + throw new IllegalArgumentException("Facades can't be empty for Facade Cache Builder!"); + } + + if (facades.size() == 1) { + return facades.get(0); + } + + return new FacadeSyncCache<>(facades); + } + + private record FacadeSyncCache(List> facades) implements Cache { + + @Nullable + @Override + public V get(@Nonnull K key) { + for (var facade : facades) { + final V v = facade.get(key); + if (v != null) { + return v; + } + } + + return null; + } + + @Nonnull + @Override + public Map get(@Nonnull Collection keys) { + throw new UnsupportedOperationException(); + } + + @Nonnull + @Override + public V put(@Nonnull K key, @Nonnull V value) { + for (var facade : facades) { + facade.put(key, value); + } + + return value; + } + + @Override + public V computeIfAbsent(@Nonnull K key, @Nonnull Function mappingFunction) { + for (int i = 0; i < facades.size(); i++) { + var facade = facades.get(i); + final V v = facade.get(key); + if (v != null) { + for (int j = 0; j < i; j++) { + var facadeToUpdate = facades.get(j); + facadeToUpdate.put(key, v); + } + + return v; + } + } + + final V computed = mappingFunction.apply(key); + for (var facade : facades) { + facade.put(key, computed); + } + + return computed; + } + + @Nonnull + @Override + public Map computeIfAbsent(@Nonnull Collection keys, @Nonnull Function, Map> mappingFunction) { + final Map> cacheToValues = new LinkedHashMap<>(); + final Map resultValues = new HashMap<>(); + final Set keysLeft = new HashSet<>(keys); + for (int i = 0; i < facades.size(); i++) { + var facade = facades.get(i); + var values = facade.get(keysLeft); + + cacheToValues.put(i, values); + resultValues.putAll(values); + keysLeft.removeAll(values.keySet()); + + if (resultValues.size() == keys.size()) { + break; + } + } + + final Map computed = (!keysLeft.isEmpty()) + ? mappingFunction.apply(keysLeft) + : Collections.emptyMap(); + + resultValues.putAll(computed); + + for (var missedFacade : cacheToValues.entrySet()) { + if (missedFacade.getValue().size() != keys.size()) { + var facade = facades.get(missedFacade.getKey()); + for (var rv : resultValues.entrySet()) { + if (!missedFacade.getValue().containsKey(rv.getKey())) { + facade.put(rv.getKey(), rv.getValue()); + } + } + } + } + + return resultValues; + } + + @Override + public void invalidate(@Nonnull K key) { + for (var facade : facades) { + facade.invalidate(key); + } + } + + @Override + public void invalidate(@Nonnull Collection keys) { + for (var facade : facades) { + facade.invalidate(keys); + } + } + + @Override + public void invalidateAll() { + for (var facade : facades) { + facade.invalidateAll(); + } + } + + @Nonnull + @Override + public Mono getAsync(@Nonnull K key) { + Mono result = null; + for (var facade : facades) { + result = (result == null) + ? facade.getAsync(key) + : result.switchIfEmpty(facade.getAsync(key)); + } + + return result; + } + + @Nonnull + @Override + public Mono> getAsync(@Nonnull Collection keys) { + throw new UnsupportedOperationException(); + } + + @Nonnull + @Override + public Mono putAsync(@Nonnull K key, @Nonnull V value) { + final List> operations = facades.stream() + .map(cache -> cache.putAsync(key, value)) + .toList(); + + return Flux.merge(operations).then(Mono.just(value)); + } + + @Override + public Mono computeIfAbsentAsync(@Nonnull K key, @Nonnull Function> mappingFunction) { + Mono result = facades.get(0).getAsync(key); + for (int i = 1; i < facades.size(); i++) { + final int currentFacade = i; + var facade = facades.get(i); + result = result.switchIfEmpty(facade.getAsync(key) + .flatMap(received -> { + final List> operations = new ArrayList<>(); + for (int j = 0; j < currentFacade; j++) { + operations.add(facades.get(j).putAsync(key, received)); + } + return Flux.merge(operations).then(Mono.just(received)); + })); + } + + return result.switchIfEmpty(mappingFunction.apply(key) + .flatMap(computed -> putAsync(key, computed))); + } + + record ComputeResult(Map result, Map> cacheToValues) {} + + @Nonnull + @Override + public Mono> computeIfAbsentAsync(@Nonnull Collection keys, @Nonnull Function, Mono>> mappingFunction) { + return Mono.defer(() -> { + Mono> result = Mono.just(new ComputeResult<>(new HashMap<>(), new HashMap<>())); + for (int i = 0; i < facades.size(); i++) { + final int currentFacade = i; + var facade = facades.get(i); + result = result.flatMap(received -> { + if (received.result().size() == keys.size()) { + return Mono.just(received); + } + + final Set keysLeft = keys.stream() + .filter(k -> !received.result().containsKey(k)) + .collect(Collectors.toSet()); + + return facade.getAsync(keysLeft) + .map(receivedNow -> { + var cacheToValuesNow = new HashMap<>(received.cacheToValues()); + cacheToValuesNow.put(currentFacade, receivedNow); + var resultNow = new HashMap<>(receivedNow); + resultNow.putAll(received.result()); + return new ComputeResult<>(resultNow, cacheToValuesNow); + }) + .switchIfEmpty(Mono.just(received)); + }); + } + + return result.flatMap(received -> { + if (received.result().size() == keys.size()) { + return Mono.just(received.result()); + } + + final Set keysLeft = keys.stream() + .filter(k -> !received.result().containsKey(k)) + .collect(Collectors.toSet()); + + return mappingFunction.apply(keysLeft) + .switchIfEmpty(Mono.just(Collections.emptyMap())) + .flatMap(computed -> { + var resultFinal = new HashMap<>(computed); + resultFinal.putAll(received.result()); + + final List> puts = received.cacheToValues().entrySet().stream() + .flatMap(e -> { + var facade = facades.get(e.getKey()); + return resultFinal.entrySet().stream() + .filter(resultEntry -> !e.getValue().containsKey(resultEntry.getKey())) + .map(resultEntry -> facade.putAsync(resultEntry.getKey(), resultEntry.getValue())); + }) + .toList(); + + return Flux.merge(puts).then(Mono.just(resultFinal)); + }); + }); + }); + } + + @Nonnull + @Override + public Mono invalidateAsync(@Nonnull K key) { + final List> operations = facades.stream() + .map(cache -> cache.invalidateAsync(key)) + .toList(); + + return Flux.merge(operations).reduce((v1, v2) -> v1 && v2); + } + + @Override + public Mono invalidateAsync(@Nonnull Collection keys) { + final List> operations = facades.stream() + .map(cache -> cache.invalidateAsync(keys)) + .toList(); + + return Flux.merge(operations).reduce((v1, v2) -> v1 && v2); + } + + @Nonnull + @Override + public Mono invalidateAllAsync() { + final List> operations = facades.stream() + .map(Cache::invalidateAllAsync) + .toList(); + + return Flux.merge(operations).reduce((v1, v2) -> v1 && v2); + } + } +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheManagerBuilder.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheManagerBuilder.java deleted file mode 100644 index b848e6735..000000000 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/FacadeCacheManagerBuilder.java +++ /dev/null @@ -1,175 +0,0 @@ -package ru.tinkoff.kora.cache; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -final class FacadeCacheManagerBuilder implements CacheManager.Builder { - - private final List> facades = new ArrayList<>(); - - @Nonnull - @Override - public CacheManager.Builder addFacadeManager(@Nonnull CacheManager cacheManager) { - facades.add(new ManagerFacade<>(cacheManager)); - return this; - } - - @Nonnull - @Override - public CacheManager.Builder addFacadeFunction(@Nonnull Function> cacheFunction) { - facades.add(new FunctionFacade<>(cacheFunction)); - return this; - } - - @Nonnull - @Override - public CacheManager build() { - if (facades.isEmpty()) { - throw new IllegalArgumentException("Facades can't be empty for Facade Cache Manager!"); - } - - if (facades.size() == 1) { - return new SingleFacadeCacheManager<>(facades.get(0)); - } - - return new SimpleFacadeCacheManager<>(facades); - } - - private interface Facade { - Cache getCache(@Nonnull String name); - } - - private record ManagerFacade(CacheManager manager) implements Facade { - @Override - public Cache getCache(@Nonnull String name) { - return manager.getCache(name); - } - } - - private record FunctionFacade(Function> function, Map> cacheMap) implements Facade { - - private FunctionFacade(Function> function) { - this(function, new HashMap<>()); - } - - @Override - public Cache getCache(@Nonnull String name) { - return cacheMap.computeIfAbsent(name, function); - } - } - - private record FacadeCache(String name, List> facades) implements Cache { - - @Nullable - @Override - public V get(@Nonnull K key) { - for (Facade facade : facades) { - final Cache cache = facade.getCache(name); - final V v = cache.get(key); - if (v != null) { - return v; - } - } - - return null; - } - - @Nonnull - @Override - public Mono getAsync(@Nonnull K key) { - Mono result = null; - for (Facade facade : facades) { - final Cache cache = facade.getCache(name); - result = (result == null) - ? cache.getAsync(key) - : result.switchIfEmpty(cache.getAsync(key)); - } - - return result; - } - - @Nonnull - @Override - public V put(@Nonnull K key, @Nonnull V value) { - for (Facade facade : facades) { - final Cache cache = facade.getCache(name); - cache.put(key, value); - } - - return value; - } - - @Nonnull - @Override - public Mono putAsync(@Nonnull K key, @Nonnull V value) { - final List> operations = facades.stream() - .map(f -> f.getCache(name)) - .map(cache -> cache.putAsync(key, value)) - .toList(); - - return Flux.merge(operations).then(Mono.just(value)); - } - - @Override - public void invalidate(@Nonnull K key) { - for (Facade facade : facades) { - final Cache cache = facade.getCache(name); - cache.invalidate(key); - } - } - - @Nonnull - @Override - public Mono invalidateAsync(@Nonnull K key) { - final List> operations = facades.stream() - .map(f -> f.getCache(name)) - .map(cache -> cache.invalidateAsync(key)) - .toList(); - - return Flux.merge(operations).then(); - } - - @Override - public void invalidateAll() { - for (Facade facade : facades) { - final Cache cache = facade.getCache(name); - cache.invalidateAll(); - } - } - - @Nonnull - @Override - public Mono invalidateAllAsync() { - final List> operations = facades.stream() - .map(f -> f.getCache(name)) - .map(Cache::invalidateAllAsync) - .toList(); - - return Flux.merge(operations).then(); - } - } - - private record SingleFacadeCacheManager(Facade facade) implements CacheManager { - - @Override - public Cache getCache(@Nonnull String name) { - return facade.getCache(name); - } - } - - private record SimpleFacadeCacheManager(List> facades) implements CacheManager { - - @Override - public Cache getCache(@Nonnull String name) { - return new FacadeCache<>(name, facades); - } - } -} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCache.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCache.java index 96c838805..dceec4a26 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCache.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCache.java @@ -2,10 +2,10 @@ import reactor.core.publisher.Mono; -import javax.annotation.Nullable; import javax.annotation.Nonnull; -import java.util.concurrent.ExecutorService; -import java.util.function.Function; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; /** * Analog of Caffeine LoadableCache, require {@link CacheLoader} associated @@ -21,6 +21,9 @@ public interface LoadableCache { @Nullable V get(@Nonnull K key); + @Nonnull + Map get(@Nonnull Collection keys); + /** * Resolve the given value for the given key. * @@ -30,56 +33,6 @@ public interface LoadableCache { @Nonnull Mono getAsync(@Nonnull K key); - /** - * Create default loadable cache implementation - * @param cache Cache to store loaded value - * @param cacheLoader Cache loader - * @return default loadable cache instance - * @param Cache key - * @param Cache value - */ - @Nonnull - static LoadableCache create(Cache cache, CacheLoader cacheLoader) { - return new DefaultLoadableCache<>(cache, cacheLoader); - } - - /** - * Create default loadable cache implementation for blocking operation - * @param cache Cache to store loaded value - * @param executor Executor to submit load task on async call - * @param loader Blocking load operation - * @return default loadable cache instance - * @param Cache key - * @param Cache value - */ - @Nonnull - static LoadableCache blocking(Cache cache, ExecutorService executor, Function loader) { - return create(cache, new CacheLoader.BlockingCacheLoader<>(executor, loader)); - } - - /** - * Create default loadable cache implementation for non-blocking operation - * @param cache Cache to store loaded value - * @param loader Some long computation - * @return default loadable cache instance - * @param Cache key - * @param Cache value - */ - @Nonnull - static LoadableCache nonBlocking(Cache cache, Function loader) { - return create(cache, new CacheLoader.NonBlockingCacheLoader<>(loader)); - } - - /** - * Create default loadable cache implementation for async operation - * @param cache Cache to store loaded value - * @param loader Async - * @return default loadable cache instance - * @param Cache key - * @param Cache value - */ @Nonnull - static LoadableCache async(Cache cache, Function> loader) { - return create(cache, new CacheLoader.AsyncCacheLoader<>(loader)); - } + Mono> getAsync(@Nonnull Collection keys); } diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheImpl.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheImpl.java new file mode 100644 index 000000000..5f38d6156 --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheImpl.java @@ -0,0 +1,43 @@ +package ru.tinkoff.kora.cache; + +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; + +final class LoadableCacheImpl implements LoadableCache { + + private final Cache cache; + private final CacheLoader cacheLoader; + + LoadableCacheImpl(Cache cache, CacheLoader cacheLoader) { + this.cache = cache; + this.cacheLoader = cacheLoader; + } + + @Nullable + @Override + public V get(@Nonnull K key) { + return cache.computeIfAbsent(key, cacheLoader::load); + } + + @Nonnull + @Override + public Map get(@Nonnull Collection keys) { + return cache.computeIfAbsent(keys, cacheLoader::load); + } + + @Nonnull + @Override + public Mono getAsync(@Nonnull K key) { + return cache.computeIfAbsentAsync(key, cacheLoader::loadAsync); + } + + @Nonnull + @Override + public Mono> getAsync(@Nonnull Collection keys) { + return cache.computeIfAbsentAsync(keys, cacheLoader::loadAsync); + } +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheManager.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheManager.java deleted file mode 100644 index 60e636063..000000000 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/LoadableCacheManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.tinkoff.kora.cache; - -import javax.annotation.Nonnull; - -/** - * {@link LoadableCache} manager contract that is responsible for retrieval Cache implementations. - */ -public interface LoadableCacheManager { - - /** - * @param name cache name - * @return identified {@link LoadableCache} if it exists or creates new one - */ - LoadableCache getLoadableCache(@Nonnull String name); -} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cache.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cache.java new file mode 100644 index 000000000..f23a5e000 --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cache.java @@ -0,0 +1,16 @@ +package ru.tinkoff.kora.cache.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface Cache { + + /** + * @return path for cache config (cache name) + */ + String value(); +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidate.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidate.java index 76f714df8..eae76dc18 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidate.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidate.java @@ -1,5 +1,6 @@ package ru.tinkoff.kora.cache.annotation; +import ru.tinkoff.kora.cache.Cache; import ru.tinkoff.kora.cache.CacheKeyMapper; import ru.tinkoff.kora.common.AopAnnotation; @@ -11,19 +12,14 @@ */ @Repeatable(CacheInvalidates.class) @Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @AopAnnotation public @interface CacheInvalidate { /** * @return cache name (correlate with name in configuration file) */ - String name(); - - /** - * @return {@link ru.tinkoff.kora.cache.CacheManager} implementation associated with cache (will use impl from default module if any present in Graph) - */ - Class[] tags() default {}; + Class> value(); /** * Limit the automatic {@link CacheKeyMapper} to the given parameter names. Mutually exclusive with diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidates.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidates.java index d5b9d945b..ceede51c6 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidates.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CacheInvalidates.java @@ -11,7 +11,7 @@ * @see CacheInvalidate */ @Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @AopAnnotation public @interface CacheInvalidates { diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePut.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePut.java index 2340e7598..593c172db 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePut.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePut.java @@ -1,5 +1,6 @@ package ru.tinkoff.kora.cache.annotation; +import ru.tinkoff.kora.cache.Cache; import ru.tinkoff.kora.cache.CacheKeyMapper; import ru.tinkoff.kora.common.AopAnnotation; @@ -12,19 +13,14 @@ */ @Repeatable(CachePuts.class) @Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @AopAnnotation public @interface CachePut { /** * @return cache name (correlate with name in configuration file) */ - String name(); - - /** - * @return {@link ru.tinkoff.kora.cache.CacheManager} implementation associated with cache (will use impl from default module if any present in Graph) - */ - Class[] tags() default {}; + Class> value(); /** * Limit the automatic {@link CacheKeyMapper} to the given parameter names. Mutually exclusive with diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePuts.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePuts.java index 3566d6776..59ce6c1bf 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePuts.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/CachePuts.java @@ -11,7 +11,7 @@ * @see CachePut */ @Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @AopAnnotation public @interface CachePuts { diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheable.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheable.java index 4f5a53c2b..32ef4e839 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheable.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheable.java @@ -1,5 +1,6 @@ package ru.tinkoff.kora.cache.annotation; +import ru.tinkoff.kora.cache.Cache; import ru.tinkoff.kora.cache.CacheKeyMapper; import ru.tinkoff.kora.common.AopAnnotation; @@ -7,23 +8,18 @@ /** * An annotation that can be applied at the type or method level to indicate that the return value of the method - * should be cached for the configured {@link Cacheable#name()}. + * should be cached for the configured {@link Cacheable#value()}. */ @Repeatable(Cacheables.class) @Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @AopAnnotation public @interface Cacheable { /** * @return cache name (correlate with name in configuration file) */ - String name(); - - /** - * @return {@link ru.tinkoff.kora.cache.CacheManager} implementation associated with cache (will use impl from default module if any present in Graph) - */ - Class[] tags() default {}; + Class> value(); /** * Limit the automatic {@link CacheKeyMapper} to the given parameter names. Mutually exclusive with diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheables.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheables.java index 6dde49e1a..c131192af 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheables.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/annotation/Cacheables.java @@ -11,7 +11,7 @@ * @see Cacheable */ @Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) +@Retention(RetentionPolicy.CLASS) @AopAnnotation public @interface Cacheables { diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheMetrics.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheMetrics.java index 2a586a7fa..cbb0e6889 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheMetrics.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheMetrics.java @@ -5,7 +5,7 @@ public interface CacheMetrics { - void recordSuccess(@Nonnull CacheTelemetry.Operation operation, long durationInNanos, @Nullable Object valueFromCache); + void recordSuccess(@Nonnull CacheTelemetryOperation operation, long durationInNanos, @Nullable Object valueFromCache); - void recordFailure(@Nonnull CacheTelemetry.Operation operation, long durationInNanos, @Nullable Throwable throwable); + void recordFailure(@Nonnull CacheTelemetryOperation operation, long durationInNanos, @Nullable Throwable throwable); } diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetry.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetry.java deleted file mode 100644 index 818b3bd38..000000000 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetry.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.tinkoff.kora.cache.telemetry; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public interface CacheTelemetry { - - record Operation(@Nonnull Type type, @Nonnull String cacheName, @Nonnull String origin) { - - public enum Type { - GET, - PUT, - INVALIDATE, - INVALIDATE_ALL - } - } - - interface TelemetryContext { - void startRecording(); - - void recordSuccess(); - - void recordSuccess(@Nullable Object valueFromCache); - - void recordFailure(@Nullable Throwable throwable); - } - - @Nonnull - TelemetryContext create(@Nonnull Operation.Type type, @Nonnull String cacheName, @Nonnull String origin); -} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetryOperation.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetryOperation.java new file mode 100644 index 000000000..fb1d1f317 --- /dev/null +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTelemetryOperation.java @@ -0,0 +1,14 @@ +package ru.tinkoff.kora.cache.telemetry; + +import javax.annotation.Nonnull; + +public interface CacheTelemetryOperation { + @Nonnull + String name(); + + @Nonnull + String cacheName(); + + @Nonnull + String origin(); +} diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTracer.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTracer.java index 8c7e6ec5b..30dadedf9 100644 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTracer.java +++ b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/CacheTracer.java @@ -12,5 +12,5 @@ interface CacheSpan { void recordFailure(@Nullable Throwable throwable); } - CacheSpan trace(@Nonnull CacheTelemetry.Operation operation); + CacheSpan trace(@Nonnull CacheTelemetryOperation operation); } diff --git a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/DefaultCacheTelemetry.java b/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/DefaultCacheTelemetry.java deleted file mode 100644 index 2488d5dfb..000000000 --- a/cache/cache-common/src/main/java/ru/tinkoff/kora/cache/telemetry/DefaultCacheTelemetry.java +++ /dev/null @@ -1,95 +0,0 @@ -package ru.tinkoff.kora.cache.telemetry; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -public final class DefaultCacheTelemetry implements CacheTelemetry { - - private static final TelemetryContext STUB_CONTEXT = new StubCacheTelemetry(); - - @Nullable - private final CacheMetrics metrics; - @Nullable - private final CacheTracer tracer; - private final boolean isStubTelemetry; - - public DefaultCacheTelemetry(@Nullable CacheMetrics metrics, @Nullable CacheTracer tracer) { - this.metrics = metrics; - this.tracer = tracer; - this.isStubTelemetry = metrics == null && tracer == null; - } - - record StubCacheTelemetry() implements TelemetryContext { - @Override - public void startRecording() {} - - @Override - public void recordSuccess() {} - - @Override - public void recordSuccess(@Nullable Object valueFromCache) {} - - @Override - public void recordFailure(@Nullable Throwable throwable) {} - } - - class DefaultCacheTelemetryContext implements TelemetryContext { - - private final Operation operation; - - private CacheTracer.CacheSpan span; - private long startedInNanos = -1; - - DefaultCacheTelemetryContext(Operation operation) { - this.operation = operation; - } - - @Override - public void startRecording() { - if (startedInNanos == -1) { - startedInNanos = System.nanoTime(); - - if (tracer != null) { - span = tracer.trace(operation); - } - } - } - - @Override - public void recordSuccess() { - recordSuccess(null); - } - - @Override - public void recordSuccess(@Nullable Object valueFromCache) { - if (metrics != null) { - final long durationInNanos = System.nanoTime() - startedInNanos; - metrics.recordSuccess(operation, durationInNanos, valueFromCache); - } - if (span != null) { - span.recordSuccess(); - } - } - - @Override - public void recordFailure(@Nullable Throwable throwable) { - if (metrics != null) { - final long durationInNanos = System.nanoTime() - startedInNanos; - metrics.recordFailure(operation, durationInNanos, throwable); - } - if (span != null) { - span.recordFailure(throwable); - } - } - } - - @Nonnull - @Override - public TelemetryContext create(@Nonnull Operation.Type type, @Nonnull String cacheName, @Nonnull String origin) { - if (isStubTelemetry) { - return STUB_CONTEXT; - } - - return new DefaultCacheTelemetryContext(new Operation(type, cacheName, origin)); - } -} diff --git a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/MonoCacheAopTests.java b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/MonoCacheAopTests.java index d330abaad..86f6f95c9 100644 --- a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/MonoCacheAopTests.java +++ b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/MonoCacheAopTests.java @@ -5,150 +5,122 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import ru.tinkoff.kora.cache.testcache.DummyCache; -import ru.tinkoff.kora.cache.testcache.DummyCacheManager; - -import java.time.Duration; @TestInstance(TestInstance.Lifecycle.PER_CLASS) class MonoCacheAopTests extends Assertions { - private final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - private final DummyCache cacheFacade2 = new DummyCache<>("test"); + private final DummyCache cache1 = new DummyCache("cache1"); @BeforeEach void cleanup() { - cacheFacade1.reset(); - cacheFacade2.invalidateAll(); + cache1.invalidateAll(); } @Test - void getFromCacheWhenWasCacheEmpty() { + void getWhenCacheEmpty() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); - // when // then - final Cache facadeCache = service.getCache("test"); - assertNull(facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); + assertNull(facade.getAsync("key1").block()); } @Test - void getFromCacheForFacade1() { + void getForFacade1() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); // when final String result = "value1"; - cacheFacade1.getCache("test").put("key1", result); + cache1.putAsync("key1", result).block(); // then - final Cache facadeCache = service.getCache("test"); - assertEquals(result, facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); + assertEquals(result, facade.getAsync("key1").block()); } @Test - void getFromCacheForFacade2() { + void getForFacade2() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); // when final String result = "value1"; - cacheFacade2.put("key1", result); + cache2.putAsync("key1", result).block(); // then - final Cache facadeCache = service.getCache("test"); - assertEquals(result, facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); + assertEquals(result, facade.getAsync("key1").block()); } @Test - void putCacheForFacade() { + void putForFacade12() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); // when - final Cache facadeCache = service.getCache("test"); final String result = "value1"; - facadeCache.putAsync("key1", result).block(Duration.ofSeconds(5)); + facade.putAsync("key1", result).block(); // then - assertEquals(result, facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); - assertEquals(result, cacheFacade1.getCache("test").get("key1")); - assertEquals(result, cacheFacade2.get("key1")); + assertEquals(result, facade.getAsync("key1").block()); + assertEquals(result, cache1.getAsync("key1").block()); + assertEquals(result, cache2.getAsync("key1").block()); } @Test void invalidateCacheForFacade() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); final String result = "value1"; - cacheFacade1.getCache("test").put("key1", result); - cacheFacade2.put("key1", result); + facade.putAsync("key1", result).block(); + assertEquals(result, facade.getAsync("key1").block()); + assertEquals(result, cache1.getAsync("key1").block()); + assertEquals(result, cache2.getAsync("key1").block()); // when - final Cache facadeCache = service.getCache("test"); - assertNotNull(facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); - facadeCache.invalidateAsync("key1").block(Duration.ofSeconds(5)); + facade.invalidateAsync("key1").block(); // then - assertNull(facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); - assertNull(cacheFacade1.getCache("test").get("key1")); - assertNull(cacheFacade2.get("key1")); + assertNull(facade.getAsync("key1").block()); + assertNull(cache1.getAsync("key1").block()); + assertNull(cache2.getAsync("key1").block()); } @Test void invalidateAllCacheForFacade() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); final String result = "value1"; - cacheFacade1.getCache("test").put("key1", result); - cacheFacade2.put("key1", result); + facade.putAsync("key1", result).block(); + assertEquals(result, facade.getAsync("key1").block()); + assertEquals(result, cache1.getAsync("key1").block()); + assertEquals(result, cache2.getAsync("key1").block()); // when - final Cache facadeCache = service.getCache("test"); - assertNotNull(facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); - facadeCache.invalidateAllAsync().block(Duration.ofSeconds(5)); + facade.invalidateAllAsync().block(); // then - assertNull(facadeCache.getAsync("key1").block(Duration.ofSeconds(5))); - assertNull(cacheFacade1.getCache("test").get("key1")); - assertNull(cacheFacade2.get("key1")); + assertNull(facade.getAsync("key1").block()); + assertNull(cache1.getAsync("key1").block()); + assertNull(cache2.getAsync("key1").block()); } } diff --git a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/SyncCacheAopTests.java b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/SyncCacheAopTests.java index 1df2faf4a..d504a6efe 100644 --- a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/SyncCacheAopTests.java +++ b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/SyncCacheAopTests.java @@ -5,150 +5,122 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import ru.tinkoff.kora.cache.testcache.DummyCache; -import ru.tinkoff.kora.cache.testcache.DummyCacheManager; - -import java.util.function.Function; @TestInstance(TestInstance.Lifecycle.PER_CLASS) class SyncCacheAopTests extends Assertions { - private final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - private final DummyCache cacheFacade2 = new DummyCache<>("test"); + private final DummyCache cache1 = new DummyCache("cache1"); @BeforeEach void cleanup() { - cacheFacade1.reset(); - cacheFacade2.invalidateAll(); + cache1.invalidateAll(); } @Test - void getFromCacheWhenWasCacheEmpty() { + void getWhenCacheEmpty() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(n -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); - // when // then - final Cache facadeCache = service.getCache("test"); - assertNull(facadeCache.get("key1")); + assertNull(facade.get("key1")); } @Test - void getFromCacheForFacade1() { + void getForFacade1() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); // when final String result = "value1"; - cacheFacade1.getCache("test").put("key1", result); + cache1.put("key1", result); // then - final Cache facadeCache = service.getCache("test"); - assertEquals(result, facadeCache.get("key1")); + assertEquals(result, facade.get("key1")); } @Test - void getFromCacheForFacade2() { + void getForFacade2() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); // when final String result = "value1"; - cacheFacade2.put("key1", result); + cache2.put("key1", result); // then - final Cache facadeCache = service.getCache("test"); - assertEquals(result, facadeCache.get("key1")); + assertEquals(result, facade.get("key1")); } @Test - void putCacheForFacade() { + void putForFacade12() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); // when - final Cache facadeCache = service.getCache("test"); final String result = "value1"; - facadeCache.put("key1", result); + facade.put("key1", result); // then - assertEquals(result, facadeCache.get("key1")); - assertEquals(result, cacheFacade1.getCache("test").get("key1")); - assertEquals(result, cacheFacade2.get("key1")); + assertEquals(result, facade.get("key1")); + assertEquals(result, cache1.get("key1")); + assertEquals(result, cache2.get("key1")); } @Test void invalidateCacheForFacade() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); final String result = "value1"; - cacheFacade1.getCache("test").put("key1", result); - cacheFacade2.put("key1", result); + facade.put("key1", result); + assertEquals(result, facade.get("key1")); + assertEquals(result, cache1.get("key1")); + assertEquals(result, cache2.get("key1")); // when - final Cache facadeCache = service.getCache("test"); - assertNotNull(facadeCache.get("key1")); - facadeCache.invalidate("key1"); + facade.invalidate("key1"); // then - assertNull(facadeCache.get("key1")); - assertNull(cacheFacade1.getCache("test").get("key1")); - assertNull(cacheFacade2.get("key1")); + assertNull(facade.get("key1")); + assertNull(cache1.get("key1")); + assertNull(cache2.get("key1")); } @Test void invalidateAllCacheForFacade() { // given - final DummyCacheManager cacheFacade1 = new DummyCacheManager<>(); - final DummyCache cacheFacade2 = new DummyCache<>("test"); - final CacheManager.Builder builder = CacheManager.builder(); - final CacheManager service = builder - .addFacadeManager(cacheFacade1) - .addFacadeFunction(name -> cacheFacade2) + final DummyCache cache2 = new DummyCache("cache2"); + final Cache facade = CacheBuilder.builder(cache1) + .addCache(cache2) .build(); final String result = "value1"; - cacheFacade1.getCache("test").put("key1", result); - cacheFacade2.put("key1", result); + facade.put("key1", result); + assertEquals(result, facade.get("key1")); + assertEquals(result, cache1.get("key1")); + assertEquals(result, cache2.get("key1")); // when - final Cache facadeCache = service.getCache("test"); - assertNotNull(facadeCache.get("key1")); - facadeCache.invalidateAll(); + facade.invalidateAll(); // then - assertNull(facadeCache.get("key1")); - assertNull(cacheFacade1.getCache("test").get("key1")); - assertNull(cacheFacade2.get("key1")); + assertNull(facade.get("key1")); + assertNull(cache1.get("key1")); + assertNull(cache2.get("key1")); } } diff --git a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCache.java b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCache.java index 842dd1a1a..290c4bdf2 100644 --- a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCache.java +++ b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCache.java @@ -2,45 +2,51 @@ import reactor.core.publisher.Mono; import ru.tinkoff.kora.cache.Cache; -import ru.tinkoff.kora.cache.LoadableCache; import javax.annotation.Nonnull; +import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; -public class DummyCache implements Cache { +public class DummyCache implements Cache { - private final String name; - private final Map cache = new HashMap<>(); + private final Map cache = new HashMap<>(); public DummyCache(String name) { - this.name = name; + } - @Nonnull - String origin() { - return "dummy"; + @Override + public String get(@Nonnull String key) { + return cache.get(key); } @Nonnull - String name() { - return name; + @Override + public String put(@Nonnull String key, @Nonnull String value) { + cache.put(key, value); + return value; } @Override - public V get(@Nonnull K key) { - return cache.get(key); + public String computeIfAbsent(@Nonnull String key, @Nonnull Function mappingFunction) { + return cache.computeIfAbsent(key, mappingFunction); } @Nonnull @Override - public V put(@Nonnull K key, @Nonnull V value) { - cache.put(key, value); - return value; + public Map computeIfAbsent(@Nonnull Collection keys, @Nonnull Function, Map> mappingFunction) { + return keys.stream() + .map(k -> Map.of(k, cache.computeIfAbsent(k, key -> mappingFunction.apply(Set.of(key)).get(key)))) + .flatMap(m -> m.entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override - public void invalidate(@Nonnull K key) { + public void invalidate(@Nonnull String key) { cache.remove(key); } @@ -51,28 +57,67 @@ public void invalidateAll() { @Nonnull @Override - public Mono getAsync(@Nonnull K key) { + public Mono getAsync(@Nonnull String key) { return Mono.justOrEmpty(get(key)); } @Nonnull @Override - public Mono putAsync(@Nonnull K key, @Nonnull V value) { + public Mono putAsync(@Nonnull String key, @Nonnull String value) { put(key, value); return Mono.just(value); } + @Override + public Mono computeIfAbsentAsync(@Nonnull String key, @Nonnull Function> mappingFunction) { + return Mono.just(computeIfAbsent(key, (k) -> mappingFunction.apply(k).block())); + } + @Nonnull @Override - public Mono invalidateAsync(@Nonnull K key) { + public Mono> computeIfAbsentAsync(@Nonnull Collection keys, @Nonnull Function, Mono>> mappingFunction) { + return Mono.just(computeIfAbsent(keys, (k) -> mappingFunction.apply(k).block())); + } + + @Nonnull + @Override + public Mono invalidateAsync(@Nonnull String key) { invalidate(key); - return Mono.empty(); + return Mono.just(true); } @Nonnull @Override - public Mono invalidateAllAsync() { + public Mono invalidateAllAsync() { invalidateAll(); - return Mono.empty(); + return Mono.just(true); + } + + @Override + public void invalidate(@Nonnull Collection keys) { + for (String key : keys) { + invalidate(key); + } + } + + @Override + public Mono invalidateAsync(@Nonnull Collection keys) { + for (String key : keys) { + invalidate(key); + } + return Mono.just(true); + } + + @Nonnull + @Override + public Map get(@Nonnull Collection keys) { + return keys.stream() + .collect(Collectors.toMap(k -> k, cache::get)); + } + + @Nonnull + @Override + public Mono> getAsync(@Nonnull Collection keys) { + return Mono.just(get(keys)); } } diff --git a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCacheManager.java b/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCacheManager.java deleted file mode 100644 index 1e23ed93e..000000000 --- a/cache/cache-common/src/test/java/ru/tinkoff/kora/cache/testcache/DummyCacheManager.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.tinkoff.kora.cache.testcache; - -import ru.tinkoff.kora.cache.Cache; -import ru.tinkoff.kora.cache.CacheManager; -import ru.tinkoff.kora.cache.LoadableCache; - -import javax.annotation.Nonnull; -import java.util.HashMap; -import java.util.Map; - -public class DummyCacheManager implements CacheManager { - - private final Map> cacheMap = new HashMap<>(); - - @Override - public Cache getCache(@Nonnull String name) { - return cacheMap.computeIfAbsent(name, k -> new DummyCache<>(name)); - } - - public void reset() { - cacheMap.clear(); - } -} diff --git a/cache/cache-redis/build.gradle b/cache/cache-redis/build.gradle index d61f88415..8d819aef7 100644 --- a/cache/cache-redis/build.gradle +++ b/cache/cache-redis/build.gradle @@ -1,4 +1,6 @@ dependencies { + compileOnly libs.jetbrains.annotations + api project(":cache:cache-common") annotationProcessor project(':config:config-annotation-processor') @@ -22,4 +24,4 @@ dependencies { testImplementation libs.testcontainers.junit.jupiter } -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle" diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/AbstractRedisCache.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/AbstractRedisCache.java new file mode 100644 index 000000000..8bd0f6c49 --- /dev/null +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/AbstractRedisCache.java @@ -0,0 +1,401 @@ +package ru.tinkoff.kora.cache.redis; + +import org.jetbrains.annotations.ApiStatus.Internal; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import ru.tinkoff.kora.cache.Cache; +import ru.tinkoff.kora.cache.redis.client.ReactiveRedisClient; +import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Internal +public abstract class AbstractRedisCache implements Cache { + + private final String name; + private final SyncRedisClient syncClient; + private final ReactiveRedisClient reactiveClient; + private final RedisCacheTelemetry telemetry; + + private final RedisCacheKeyMapper keyMapper; + private final RedisCacheValueMapper valueMapper; + + private final Long expireAfterAccessMillis; + private final Long expireAfterWriteMillis; + + protected AbstractRedisCache(String name, + RedisCacheConfig config, + SyncRedisClient syncClient, + ReactiveRedisClient reactiveClient, + RedisCacheTelemetry telemetry, + RedisCacheKeyMapper keyMapper, + RedisCacheValueMapper valueMapper) { + this.name = name; + this.syncClient = syncClient; + this.reactiveClient = reactiveClient; + this.telemetry = telemetry; + this.keyMapper = keyMapper; + this.valueMapper = valueMapper; + this.expireAfterAccessMillis = (config.expireAfterAccess() == null) + ? null + : config.expireAfterAccess().toMillis(); + this.expireAfterWriteMillis = (config.expireAfterWrite() == null) + ? null + : config.expireAfterWrite().toMillis(); + } + + @Override + public V get(@Nonnull K key) { + if (key == null) { + return null; + } + + var telemetryContext = telemetry.create("GET", name); + try { + final byte[] keyAsBytes = keyMapper.apply(key); + final byte[] jsonAsBytes = (expireAfterAccessMillis == null) + ? syncClient.get(keyAsBytes) + : syncClient.getExpire(keyAsBytes, expireAfterAccessMillis); + + final V value = valueMapper.read(jsonAsBytes); + telemetryContext.recordSuccess(value); + return value; + } catch (Exception e) { + telemetryContext.recordFailure(e); + return null; + } + } + + @Nonnull + @Override + public Map get(@Nonnull Collection keys) { + if (keys == null || keys.isEmpty()) { + return null; + } + + var telemetryContext = telemetry.create("GET_MANY", name); + try { + final Map keysByKeyBytes = keys.stream() + .collect(Collectors.toMap(k -> k, keyMapper, (v1, v2) -> v2)); + + final byte[][] keysByBytes = keysByKeyBytes.values().toArray(byte[][]::new); + final Map valueByKeys = (expireAfterAccessMillis == null) + ? syncClient.get(keysByBytes) + : syncClient.getExpire(keysByBytes, expireAfterAccessMillis); + + final Map keyToValue = new HashMap<>(); + for (var entry : keysByKeyBytes.entrySet()) { + valueByKeys.forEach((k, v) -> { + if (Arrays.equals(entry.getValue(), k)) { + var value = valueMapper.read(v); + keyToValue.put(entry.getKey(), value); + } + }); + } + + telemetryContext.recordSuccess(keyToValue); + return keyToValue; + } catch (Exception e) { + telemetryContext.recordFailure(e); + return Collections.emptyMap(); + } + } + + @Nonnull + @Override + public V put(@Nonnull K key, @Nonnull V value) { + if (key == null || value == null) { + return null; + } + + var telemetryContext = telemetry.create("PUT", name); + + try { + final byte[] keyAsBytes = keyMapper.apply(key); + final byte[] valueAsBytes = valueMapper.write(value); + if (expireAfterWriteMillis == null) { + syncClient.set(keyAsBytes, valueAsBytes); + } else { + syncClient.setExpire(keyAsBytes, valueAsBytes, expireAfterWriteMillis); + } + telemetryContext.recordSuccess(); + return value; + } catch (Exception e) { + telemetryContext.recordFailure(e); + return value; + } + } + + @Override + public V computeIfAbsent(@Nonnull K key, @Nonnull Function mappingFunction) { + var fromCache = get(key); + if (fromCache != null) { + return fromCache; + } + + var value = mappingFunction.apply(key); + if (value != null) { + put(key, value); + } + + return value; + } + + @Nonnull + @Override + public Map computeIfAbsent(@Nonnull Collection keys, @Nonnull Function, Map> mappingFunction) { + var fromCache = get(keys); + if (fromCache.size() == keys.size()) { + return fromCache; + } + + var missingKeys = keys.stream() + .filter(k -> !fromCache.containsKey(k)) + .collect(Collectors.toSet()); + + var values = mappingFunction.apply(missingKeys); + if (values != null) { + values.forEach(this::put); + } + + var result = new HashMap<>(fromCache); + result.putAll(values); + return result; + } + + @Override + public void invalidate(@Nonnull K key) { + if (key != null) { + final byte[] keyAsBytes = keyMapper.apply(key); + var telemetryContext = telemetry.create("INVALIDATE", name); + + try { + syncClient.del(keyAsBytes); + telemetryContext.recordSuccess(); + } catch (Exception e) { + telemetryContext.recordFailure(e); + } + } + } + + @Override + public void invalidate(@Nonnull Collection keys) { + if (keys != null && !keys.isEmpty()) { + var telemetryContext = telemetry.create("INVALIDATE_MANY", name); + + try { + final byte[][] keysAsBytes = keys.stream() + .map(keyMapper) + .toArray(byte[][]::new); + + syncClient.del(keysAsBytes); + telemetryContext.recordSuccess(); + } catch (Exception e) { + telemetryContext.recordFailure(e); + } + } + } + + @Override + public void invalidateAll() { + var telemetryContext = telemetry.create("INVALIDATE_ALL", name); + + try { + syncClient.flushAll(); + telemetryContext.recordSuccess(); + } catch (Exception e) { + telemetryContext.recordFailure(e); + } + } + + @Nonnull + @Override + public Mono getAsync(@Nonnull K key) { + if (key == null) { + return Mono.empty(); + } + + return Mono.defer(() -> { + var telemetryContext = telemetry.create("GET", name); + final byte[] keyAsBytes = keyMapper.apply(key); + + Mono responseMono = (expireAfterAccessMillis == null) + ? reactiveClient.get(keyAsBytes) + : reactiveClient.getExpire(keyAsBytes, expireAfterAccessMillis); + + return responseMono.map(jsonAsBytes -> { + final V value = valueMapper.read(jsonAsBytes); + telemetryContext.recordSuccess(value); + return value; + }) + .onErrorResume(e -> { + telemetryContext.recordFailure(e); + return Mono.empty(); + }); + }); + } + + @Nonnull + @Override + public Mono> getAsync(@Nonnull Collection keys) { + if (keys == null || keys.isEmpty()) { + return Mono.just(Collections.emptyMap()); + } + + return Mono.defer(() -> { + var telemetryContext = telemetry.create("GET_MANY", name); + var keysByKeyByte = keys.stream() + .collect(Collectors.toMap(k -> k, keyMapper, (v1, v2) -> v2)); + + var keysAsBytes = keysByKeyByte.values().toArray(byte[][]::new); + var responseMono = (expireAfterAccessMillis == null) + ? reactiveClient.get(keysAsBytes) + : reactiveClient.getExpire(keysAsBytes, expireAfterAccessMillis); + + return responseMono.map(valuesByKeys -> { + final Map keyToValue = new HashMap<>(); + for (var entry : keysByKeyByte.entrySet()) { + valuesByKeys.forEach((k, v) -> { + if (Arrays.equals(entry.getValue(), k)) { + var value = valueMapper.read(v); + keyToValue.put(entry.getKey(), value); + } + }); + } + telemetryContext.recordSuccess(keyToValue); + return keyToValue; + }) + .onErrorResume(e -> { + telemetryContext.recordFailure(e); + return Mono.just(Collections.emptyMap()); + }); + }); + } + + @Nonnull + @Override + public Mono putAsync(@Nonnull K key, @Nonnull V value) { + if (key == null) { + return Mono.justOrEmpty(value); + } + + return Mono.defer(() -> { + var telemetryContext = telemetry.create("PUT", name); + final byte[] keyAsBytes = keyMapper.apply(key); + final byte[] valueAsBytes = valueMapper.write(value); + final Mono responseMono = (expireAfterWriteMillis == null) + ? reactiveClient.set(keyAsBytes, valueAsBytes) + : reactiveClient.setExpire(keyAsBytes, valueAsBytes, expireAfterWriteMillis); + + return responseMono.map(r -> value) + .switchIfEmpty(Mono.fromCallable(() -> { + telemetryContext.recordSuccess(); + return value; + })) + .onErrorResume(e -> { + telemetryContext.recordFailure(e); + return Mono.just(value); + }); + }); + } + + @Override + public Mono computeIfAbsentAsync(@Nonnull K key, @Nonnull Function> mappingFunction) { + return getAsync(key) + .switchIfEmpty(mappingFunction.apply(key) + .flatMap(value -> putAsync(key, value).thenReturn(value))); + } + + @Nonnull + @Override + public Mono> computeIfAbsentAsync(@Nonnull Collection keys, @Nonnull Function, Mono>> mappingFunction) { + return getAsync(keys) + .flatMap(fromCache -> { + if (fromCache.size() == keys.size()) { + return Mono.just(fromCache); + } + + var missingKeys = keys.stream() + .filter(k -> !fromCache.containsKey(k)) + .collect(Collectors.toSet()); + + return mappingFunction.apply(missingKeys) + .flatMap(loaded -> { + var putMonos = loaded.entrySet().stream() + .map(e -> putAsync(e.getKey(), e.getValue())) + .toList(); + + final Map result; + if (fromCache.isEmpty()) { + result = loaded; + } else { + result = new HashMap<>(fromCache); + result.putAll(loaded); + } + + return Flux.merge(putMonos).then(Mono.just(result)); + }); + }); + } + + @Nonnull + @Override + public Mono invalidateAsync(@Nonnull K key) { + if (key == null) { + return Mono.just(false); + } + + return Mono.defer(() -> { + var telemetryContext = telemetry.create("INVALIDATE", name); + final byte[] keyAsBytes = keyMapper.apply(key); + return reactiveClient.del(keyAsBytes) + .then(Mono.just(true)) + .doOnSuccess(r -> telemetryContext.recordSuccess()) + .onErrorResume(e -> { + telemetryContext.recordFailure(e); + return Mono.just(false); + }); + }); + } + + @Override + public Mono invalidateAsync(@Nonnull Collection keys) { + if (keys == null || keys.isEmpty()) { + return Mono.just(false); + } + + return Mono.defer(() -> { + var telemetryContext = telemetry.create("INVALIDATE_MANY", name); + final byte[][] keyAsBytes = keys.stream() + .distinct() + .map(keyMapper) + .toArray(byte[][]::new); + + return reactiveClient.del(keyAsBytes) + .then(Mono.just(true)) + .doOnSuccess(r -> telemetryContext.recordSuccess()) + .onErrorResume(e -> { + telemetryContext.recordFailure(e); + return Mono.just(false); + }); + }); + } + + @Nonnull + @Override + public Mono invalidateAllAsync() { + return Mono.defer(() -> { + var telemetryContext = telemetry.create("INVALIDATE_ALL", name); + return reactiveClient.flushAll() + .then(Mono.just(true)) + .doOnSuccess(r -> telemetryContext.recordSuccess()) + .onErrorResume(e -> { + telemetryContext.recordFailure(e); + return Mono.just(false); + }); + }); + } +} diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/DefaultRedisCacheModule.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/DefaultRedisCacheModule.java deleted file mode 100644 index 61abf525d..000000000 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/DefaultRedisCacheModule.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.tinkoff.kora.cache.redis; - -import ru.tinkoff.kora.application.graph.TypeRef; -import ru.tinkoff.kora.cache.CacheManager; -import ru.tinkoff.kora.cache.redis.client.ReactiveRedisClient; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; -import ru.tinkoff.kora.common.Tag; - -public interface DefaultRedisCacheModule extends RedisCacheModule { - - default CacheManager defaultRedisCacheManager(RedisKeyMapper keyMapper, - RedisValueMapper valueMapper, - SyncRedisClient syncClient, - ReactiveRedisClient reactiveClient, - RedisCacheConfig cacheConfig, - @Tag(RedisCacheManager.class) CacheTelemetry telemetry, - TypeRef keyRef, - TypeRef valueRef) { - return taggedRedisCacheManager(keyMapper, valueMapper, syncClient, reactiveClient, cacheConfig, telemetry, keyRef, valueRef); - } -} diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCache.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCache.java index 47a60d88f..f0a3a6ca4 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCache.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCache.java @@ -1,231 +1,7 @@ package ru.tinkoff.kora.cache.redis; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Mono; import ru.tinkoff.kora.cache.Cache; -import ru.tinkoff.kora.cache.redis.client.ReactiveRedisClient; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; -import javax.annotation.Nonnull; +public interface RedisCache extends Cache { -final class RedisCache implements Cache { - - private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); - - private final String name; - private final SyncRedisClient syncClient; - private final ReactiveRedisClient reactiveClient; - private final CacheTelemetry telemetry; - - private final RedisKeyMapper keyMapper; - private final RedisValueMapper valueMapper; - - private final Long expireAfterAccessMillis; - private final Long expireAfterWriteMillis; - - RedisCache(String name, - SyncRedisClient syncClient, - ReactiveRedisClient reactiveClient, - CacheTelemetry telemetry, - RedisKeyMapper keyMapper, - RedisValueMapper valueMapper, - RedisCacheConfig.NamedCacheConfig cacheConfig) { - this.name = name; - this.syncClient = syncClient; - this.reactiveClient = reactiveClient; - this.telemetry = telemetry; - this.keyMapper = keyMapper; - this.valueMapper = valueMapper; - this.expireAfterAccessMillis = (cacheConfig.expireAfterAccess() == null) - ? null - : cacheConfig.expireAfterAccess().toMillis(); - this.expireAfterWriteMillis = (cacheConfig.expireAfterWrite() == null) - ? null - : cacheConfig.expireAfterWrite().toMillis(); - } - - @Nonnull - String origin() { - return "redis"; - } - - @Override - public V get(@Nonnull K key) { - logger.trace("Cache '{}' looking for value for key: {}", name, key); - final byte[] keyAsBytes = keyMapper.apply(key); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.GET, name, origin()); - - try { - telemetryContext.startRecording(); - final byte[] jsonAsBytes = (expireAfterAccessMillis == null) - ? syncClient.get(keyAsBytes) - : syncClient.getExpire(keyAsBytes, expireAfterAccessMillis); - - if (jsonAsBytes != null) { - logger.trace("Cache '{}' no value found for key: {}", name, key); - } else { - logger.debug("Cache '{}' found value for key: {}", name, key); - } - - final V v = valueMapper.read(jsonAsBytes); - telemetryContext.recordSuccess(jsonAsBytes); - return v; - } catch (Exception e) { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - return null; - } - } - - @Nonnull - @Override - public V put(@Nonnull K key, @Nonnull V value) { - logger.trace("Cache '{}' storing for key: {}", name, key); - final byte[] keyAsBytes = keyMapper.apply(key); - final byte[] valueAsBytes = valueMapper.write(value); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.PUT, name, origin()); - - try { - telemetryContext.startRecording(); - if (expireAfterWriteMillis == null) { - syncClient.set(keyAsBytes, valueAsBytes); - } else { - syncClient.setExpire(keyAsBytes, valueAsBytes, expireAfterWriteMillis); - } - telemetryContext.recordSuccess(); - logger.debug("Cache '{}' stored for key: {}", name, key); - return value; - } catch (Exception e) { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - return value; - } - } - - @Override - public void invalidate(@Nonnull K key) { - logger.trace("Cache '{}' invalidating for key: {}", name, key); - final byte[] keyAsBytes = keyMapper.apply(key); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.INVALIDATE, name, origin()); - - try { - telemetryContext.startRecording(); - syncClient.del(keyAsBytes); - telemetryContext.recordSuccess(); - logger.debug("Cache '{}' invalidated for key: {}", name, key); - } catch (Exception e) { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - } - } - - @Override - public void invalidateAll() { - logger.trace("Cache '{}' invalidating all", name); - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.INVALIDATE_ALL, name, origin()); - - try { - telemetryContext.startRecording(); - syncClient.flushAll(); - telemetryContext.recordSuccess(); - logger.debug("Cache '{}' invalidated all", name); - } catch (Exception e) { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - } - } - - @Nonnull - @Override - public Mono getAsync(@Nonnull K key) { - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.GET, name, origin()); - return Mono.fromCallable(() -> keyMapper.apply(key)) - .flatMap(keyAsBytes -> { - logger.trace("Cache '{}' looking for value for key: {}", name, key); - telemetryContext.startRecording(); - return (expireAfterAccessMillis == null) - ? reactiveClient.get(keyAsBytes) - : reactiveClient.getExpire(keyAsBytes, expireAfterAccessMillis); - }) - .map(jsonAsBytes -> { - if (jsonAsBytes != null) { - logger.trace("Cache '{}' no value found for key: {}", name, key); - } else { - logger.debug("Cache '{}' found value for key: {}", name, key); - } - final V v = valueMapper.read(jsonAsBytes); - telemetryContext.recordSuccess(jsonAsBytes); - return v; - }) - .onErrorResume(e -> { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - return Mono.empty(); - }); - } - - @Nonnull - @Override - public Mono putAsync(@Nonnull K key, @Nonnull V value) { - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.PUT, name, origin()); - return Mono.fromCallable(() -> keyMapper.apply(key)) - .flatMap(keyAsBytes -> { - logger.trace("Cache '{}' storing for key: {}", name, key); - final byte[] valueAsBytes = valueMapper.write(value); - telemetryContext.startRecording(); - return (expireAfterWriteMillis == null) - ? reactiveClient.set(keyAsBytes, valueAsBytes) - : reactiveClient.setExpire(keyAsBytes, valueAsBytes, expireAfterWriteMillis); - }) - .map(r -> value) - .switchIfEmpty(Mono.fromCallable(() -> { - telemetryContext.recordSuccess(); - logger.debug("Cache '{}' stored for key: {}", name, key); - return value; - })) - .onErrorResume(e -> { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - return Mono.just(value); - }); - } - - @Nonnull - @Override - public Mono invalidateAsync(@Nonnull K key) { - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.INVALIDATE, name, origin()); - return Mono.fromCallable(() -> { - logger.trace("Cache '{}' invalidating for key: {}", name, key); - return keyMapper.apply(key); - }) - .flatMap(reactiveClient::del) - .doOnSuccess(r -> { - telemetryContext.recordSuccess(); - logger.debug("Cache '{}' invalidated for key: {}", name, key); - }) - .onErrorResume(e -> { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - return Mono.empty(); - }); - } - - @Nonnull - @Override - public Mono invalidateAllAsync() { - final CacheTelemetry.TelemetryContext telemetryContext = telemetry.create(CacheTelemetry.Operation.Type.INVALIDATE_ALL, name, origin()); - logger.trace("Cache '{}' invalidating all", name); - return reactiveClient.flushAll() - .doOnSuccess(r -> { - telemetryContext.recordSuccess(); - logger.debug("Cache '{}' invalidated all", name); - }) - .onErrorResume(e -> { - telemetryContext.recordFailure(e); - logger.warn(e.getMessage(), e); - return Mono.empty(); - }); - } } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheConfig.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheConfig.java index 98f55f84d..7d7603902 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheConfig.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheConfig.java @@ -3,37 +3,15 @@ import ru.tinkoff.kora.config.common.annotation.ConfigValueExtractor; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.time.Duration; -import java.util.Map; @ConfigValueExtractor public interface RedisCacheConfig { - String DEFAULT = "default"; - NamedCacheConfig DEFAULT_CONFIG = new NamedCacheConfig(null, null); - default Map redis() { - return Map.of(); - } + @Nullable + Duration expireAfterWrite(); - default NamedCacheConfig getByName(@Nonnull String name) { - final NamedCacheConfig defaultConfig = this.redis().getOrDefault(DEFAULT, DEFAULT_CONFIG); - final NamedCacheConfig namedConfig = this.redis().get(name); - if (namedConfig == null) { - return defaultConfig; - } - - return new NamedCacheConfig( - namedConfig.expireAfterWrite == null ? defaultConfig.expireAfterWrite : namedConfig.expireAfterWrite, - namedConfig.expireAfterAccess == null ? defaultConfig.expireAfterAccess : namedConfig.expireAfterAccess - ); - } - - - @ConfigValueExtractor - record NamedCacheConfig( - @Nullable Duration expireAfterWrite, - @Nullable Duration expireAfterAccess - ) {} + @Nullable + Duration expireAfterAccess(); } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisKeyMapper.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheKeyMapper.java similarity index 78% rename from cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisKeyMapper.java rename to cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheKeyMapper.java index 2f04ba103..dc3932872 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisKeyMapper.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheKeyMapper.java @@ -7,6 +7,6 @@ /** * Contract for converting method arguments {@link CacheKey} into the final key that will be used in Cache implementation. */ -public interface RedisKeyMapper extends Function { +public interface RedisCacheKeyMapper extends Function { } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheManager.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheManager.java deleted file mode 100644 index 2edfb39a4..000000000 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.tinkoff.kora.cache.redis; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import ru.tinkoff.kora.cache.Cache; -import ru.tinkoff.kora.cache.CacheManager; -import ru.tinkoff.kora.cache.redis.client.ReactiveRedisClient; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; - -import javax.annotation.Nonnull; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public final class RedisCacheManager implements CacheManager { - - private static final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class); - - private final Map> cacheMap = new ConcurrentHashMap<>(); - - private final SyncRedisClient syncClient; - private final ReactiveRedisClient reactiveClient; - private final RedisCacheConfig cacheConfig; - private final CacheTelemetry telemetry; - - private final RedisKeyMapper keyMapper; - private final RedisValueMapper valueMapper; - - RedisCacheManager(SyncRedisClient syncClient, - ReactiveRedisClient reactiveClient, - RedisCacheConfig cacheConfig, - CacheTelemetry telemetry, - RedisKeyMapper keyMapper, - RedisValueMapper valueMapper) { - this.syncClient = syncClient; - this.reactiveClient = reactiveClient; - this.cacheConfig = cacheConfig; - this.telemetry = telemetry; - this.keyMapper = keyMapper; - this.valueMapper = valueMapper; - } - - @Nonnull - @Override - public Cache getCache(@Nonnull String name) { - return cacheMap.computeIfAbsent(name, k -> build(name)); - } - - private RedisCache build(@Nonnull String name) { - logger.trace("Build cache for name: {}", name); - final RedisCacheConfig.NamedCacheConfig config = cacheConfig.getByName(name); - return new RedisCache<>(name, syncClient, reactiveClient, telemetry, keyMapper, valueMapper, config); - } -} diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheModule.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheModule.java index a39478cd9..a5aa21de5 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheModule.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheModule.java @@ -2,42 +2,30 @@ import ru.tinkoff.kora.application.graph.TypeRef; import ru.tinkoff.kora.cache.CacheKey; -import ru.tinkoff.kora.cache.CacheManager; import ru.tinkoff.kora.cache.redis.client.LettuceModule; -import ru.tinkoff.kora.cache.redis.client.ReactiveRedisClient; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; import ru.tinkoff.kora.cache.telemetry.CacheMetrics; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; import ru.tinkoff.kora.cache.telemetry.CacheTracer; -import ru.tinkoff.kora.cache.telemetry.DefaultCacheTelemetry; import ru.tinkoff.kora.common.DefaultComponent; -import ru.tinkoff.kora.common.Tag; -import ru.tinkoff.kora.config.common.Config; -import ru.tinkoff.kora.config.common.extractor.ConfigValueExtractor; import ru.tinkoff.kora.json.common.JsonCommonModule; import ru.tinkoff.kora.json.common.JsonReader; import ru.tinkoff.kora.json.common.JsonWriter; import javax.annotation.Nullable; import java.io.IOException; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; +import java.util.UUID; public interface RedisCacheModule extends JsonCommonModule, LettuceModule { - @Tag(RedisCacheManager.class) @DefaultComponent - default CacheTelemetry defaultCacheTelemetry(@Nullable CacheMetrics metrics, @Nullable CacheTracer tracer) { - return new DefaultCacheTelemetry(metrics, tracer); + default RedisCacheTelemetry redisCacheTelemetry(@Nullable CacheMetrics metrics, @Nullable CacheTracer tracer) { + return new RedisCacheTelemetry(metrics, tracer); } - default RedisCacheConfig redisCacheConfig(Config config, ConfigValueExtractor extractor) { - var value = config.get("cache"); - return extractor.extract(value); - } - - default RedisValueMapper redisValueMapper(JsonWriter jsonWriter, JsonReader jsonReader, TypeRef valueRef) { - return new RedisValueMapper<>() { + @DefaultComponent + default RedisCacheValueMapper redisValueMapper(JsonWriter jsonWriter, JsonReader jsonReader, TypeRef valueRef) { + return new RedisCacheValueMapper<>() { @Override public byte[] write(V value) { try { @@ -58,19 +46,143 @@ public V read(byte[] serializedValue) { }; } - default RedisKeyMapper redisKeyMapper(TypeRef keyRef) { - return c -> c.values().stream().map(String::valueOf).collect(Collectors.joining("-")).getBytes(StandardCharsets.UTF_8); + @DefaultComponent + default RedisCacheValueMapper stringRedisValueMapper() { + return new RedisCacheValueMapper<>() { + @Override + public byte[] write(String value) { + return value.getBytes(StandardCharsets.UTF_8); + } + + @Override + public String read(byte[] serializedValue) { + return (serializedValue == null) ? null : new String(serializedValue, StandardCharsets.UTF_8); + } + }; + } + + @DefaultComponent + default RedisCacheValueMapper bytesRedisValueMapper() { + return new RedisCacheValueMapper<>() { + @Override + public byte[] write(byte[] value) { + return value; + } + + @Override + public byte[] read(byte[] serializedValue) { + return serializedValue; + } + }; + } + + @DefaultComponent + default RedisCacheValueMapper intRedisValueMapper(RedisCacheKeyMapper keyMapper) { + return new RedisCacheValueMapper<>() { + @Override + public byte[] write(Integer value) { + return keyMapper.apply(value); + } + + @Override + public Integer read(byte[] serializedValue) { + if (serializedValue == null) { + return null; + } else { + int result = 0; + for (int i = 0; i < 4; i++) { + result <<= 8; + result |= (serializedValue[i] & 0xFF); + } + return result; + } + } + }; + } + + @DefaultComponent + default RedisCacheValueMapper longRedisValueMapper(RedisCacheKeyMapper keyMapper) { + return new RedisCacheValueMapper<>() { + @Override + public byte[] write(Long value) { + return keyMapper.apply(value); + } + + @Override + public Long read(byte[] serializedValue) { + if (serializedValue == null) { + return null; + } else { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= (serializedValue[i] & 0xFF); + } + return result; + } + } + }; + } + + @DefaultComponent + default RedisCacheValueMapper bigIntRedisValueMapper(RedisCacheKeyMapper keyMapper) { + return new RedisCacheValueMapper<>() { + @Override + public byte[] write(BigInteger value) { + return keyMapper.apply(value); + } + + @Override + public BigInteger read(byte[] serializedValue) { + return (serializedValue == null) ? null : new BigInteger(serializedValue); + } + }; } - @Tag(RedisCacheManager.class) - default CacheManager taggedRedisCacheManager(RedisKeyMapper keyMapper, - RedisValueMapper valueMapper, - SyncRedisClient syncClient, - ReactiveRedisClient reactiveClient, - RedisCacheConfig cacheConfig, - @Tag(RedisCacheManager.class) CacheTelemetry telemetry, - TypeRef keyRef, - TypeRef valueRef) { - return new RedisCacheManager<>(syncClient, reactiveClient, cacheConfig, telemetry, keyMapper, valueMapper); + @DefaultComponent + default RedisCacheValueMapper uuidRedisValueMapper(RedisCacheKeyMapper keyMapper) { + return new RedisCacheValueMapper<>() { + + @Override + public byte[] write(UUID value) { + return keyMapper.apply(value); + } + + @Override + public UUID read(byte[] serializedValue) { + return (serializedValue == null) ? null : new UUID(serializedValue[0], serializedValue[1]); + } + }; + } + + // Keys + @DefaultComponent + default RedisCacheKeyMapper redisKeyMapper(TypeRef keyRef) { + return c -> c.joined().getBytes(StandardCharsets.UTF_8); + } + + @DefaultComponent + default RedisCacheKeyMapper intRedisKeyMapper() { + return c -> String.valueOf(c).getBytes(StandardCharsets.UTF_8); + } + + @DefaultComponent + default RedisCacheKeyMapper longRedisKeyMapper() { + return c -> String.valueOf(c).getBytes(StandardCharsets.UTF_8); + } + + @DefaultComponent + default RedisCacheKeyMapper bigIntRedisKeyMapper() { + return c -> c.toString().getBytes(StandardCharsets.UTF_8); + } + + @DefaultComponent + default RedisCacheKeyMapper uuidRedisKeyMapper() { + return c -> c.toString().getBytes(StandardCharsets.UTF_8); + } + + @DefaultComponent + default RedisCacheKeyMapper stringRedisKeyMapper() { + return c -> c.getBytes(StandardCharsets.UTF_8); } } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheTelemetry.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheTelemetry.java new file mode 100644 index 000000000..415b6e3f1 --- /dev/null +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheTelemetry.java @@ -0,0 +1,130 @@ +package ru.tinkoff.kora.cache.redis; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.tinkoff.kora.cache.telemetry.CacheMetrics; +import ru.tinkoff.kora.cache.telemetry.CacheTelemetryOperation; +import ru.tinkoff.kora.cache.telemetry.CacheTracer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class RedisCacheTelemetry { + + private static final String ORIGIN = "redis"; + + record Operation(@Nonnull String name, @Nonnull String cacheName) implements CacheTelemetryOperation { + + @Nonnull + @Override + public String origin() { + return ORIGIN; + } + } + + interface TelemetryContext { + void recordSuccess(); + + void recordSuccess(@Nullable Object valueFromCache); + + void recordFailure(@Nullable Throwable throwable); + } + + private static final Logger logger = LoggerFactory.getLogger(RedisCacheTelemetry.class); + + private static final TelemetryContext STUB_CONTEXT = new StubCacheTelemetry(); + + @Nullable + private final CacheMetrics metrics; + @Nullable + private final CacheTracer tracer; + private final boolean isStubTelemetry; + + RedisCacheTelemetry(@Nullable CacheMetrics metrics, @Nullable CacheTracer tracer) { + this.metrics = metrics; + this.tracer = tracer; + this.isStubTelemetry = metrics == null && tracer == null; + } + + record StubCacheTelemetry() implements TelemetryContext { + + @Override + public void recordSuccess() {} + + @Override + public void recordSuccess(@Nullable Object valueFromCache) {} + + @Override + public void recordFailure(@Nullable Throwable throwable) {} + } + + class DefaultCacheTelemetryContext implements TelemetryContext { + + private final Operation operation; + + private CacheTracer.CacheSpan span; + private final long startedInNanos = System.nanoTime(); + + DefaultCacheTelemetryContext(Operation operation) { + logger.trace("Operation '{}' for cache '{}' started", operation.name(), operation.cacheName()); + if (tracer != null) { + span = tracer.trace(operation); + } + this.operation = operation; + } + + @Override + public void recordSuccess() { + recordSuccess(null); + } + + @Override + public void recordSuccess(@Nullable Object valueFromCache) { + if (metrics != null) { + final long durationInNanos = System.nanoTime() - startedInNanos; + metrics.recordSuccess(operation, durationInNanos, valueFromCache); + } + if (span != null) { + span.recordSuccess(); + } + + if (operation.name().startsWith("GET")) { + if (valueFromCache == null) { + logger.trace("Operation '{}' for cache '{}' didn't retried value", operation.name(), operation.cacheName()); + } else { + logger.debug("Operation '{}' for cache '{}' retried value", operation.name(), operation.cacheName()); + } + } else { + logger.trace("Operation '{}' for cache '{}' completed", operation.name(), operation.cacheName()); + } + } + + @Override + public void recordFailure(@Nullable Throwable throwable) { + if (metrics != null) { + final long durationInNanos = System.nanoTime() - startedInNanos; + metrics.recordFailure(operation, durationInNanos, throwable); + } + if (span != null) { + span.recordFailure(throwable); + } + + if (throwable != null) { + logger.warn("Operation '{}' failed for cache '{}' with message: {}", + operation.name(), operation.cacheName(), throwable.getMessage()); + } else { + logger.warn("Operation '{}' failed for cache '{}'", + operation.name(), operation.cacheName()); + } + } + } + + @Nonnull + TelemetryContext create(@Nonnull String operationName, @Nonnull String cacheName) { + if (isStubTelemetry) { + return STUB_CONTEXT; + } + + return new DefaultCacheTelemetryContext(new Operation(operationName, cacheName)); + } +} diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisValueMapper.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheValueMapper.java similarity index 88% rename from cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisValueMapper.java rename to cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheValueMapper.java index fc2493260..cf2037f42 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisValueMapper.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/RedisCacheValueMapper.java @@ -3,7 +3,7 @@ /** * Converts cache value into serializer value to store in cache. */ -public interface RedisValueMapper { +public interface RedisCacheValueMapper { /** * @param value to serialize diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientConfig.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientConfig.java index a3081f7e1..bd8361b91 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientConfig.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientConfig.java @@ -45,7 +45,7 @@ public ProtocolVersion protocolVersion() { return ProtocolVersion.RESP2; } else { throw new IllegalArgumentException("Unknown protocol value '" + protocol - + "', expected value one of: " + Arrays.toString(ProtocolVersion.values())); + + "', expected value one of: " + Arrays.toString(ProtocolVersion.values())); } } } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientFactory.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientFactory.java index 94f13b3f9..3a998f9df 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientFactory.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceClientFactory.java @@ -5,12 +5,14 @@ import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.RedisClusterURIUtil; import io.lettuce.core.protocol.ProtocolVersion; +import org.jetbrains.annotations.ApiStatus.Internal; import javax.annotation.Nonnull; import java.net.URI; import java.time.Duration; import java.util.List; +@Internal public final class LettuceClientFactory { @Nonnull diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceCommander.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceCommander.java index 12b531caa..b7d5fa52c 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceCommander.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceCommander.java @@ -1,14 +1,14 @@ package ru.tinkoff.kora.cache.redis.client; -import io.lettuce.core.api.StatefulConnection; import io.lettuce.core.api.reactive.RedisKeyReactiveCommands; import io.lettuce.core.api.reactive.RedisServerReactiveCommands; import io.lettuce.core.api.reactive.RedisStringReactiveCommands; import io.lettuce.core.api.sync.RedisKeyCommands; import io.lettuce.core.api.sync.RedisServerCommands; import io.lettuce.core.api.sync.RedisStringCommands; +import ru.tinkoff.kora.application.graph.Lifecycle; -public interface LettuceCommander { +public interface LettuceCommander extends Lifecycle { record Sync(RedisServerCommands serverCommands, RedisStringCommands stringCommands, diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceModule.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceModule.java index 3092b82ff..a1618b421 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceModule.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceModule.java @@ -1,6 +1,7 @@ package ru.tinkoff.kora.cache.redis.client; import io.lettuce.core.AbstractRedisClient; +import ru.tinkoff.kora.common.DefaultComponent; import ru.tinkoff.kora.config.common.Config; import ru.tinkoff.kora.config.common.extractor.ConfigValueExtractor; @@ -23,10 +24,12 @@ default LettuceCommander lettuceCommander(AbstractRedisClient redisClient) { return new DefaultLettuceCommander(redisClient); } + @DefaultComponent default SyncRedisClient lettuceCacheRedisClient(LettuceCommander commands) { return new LettuceSyncRedisClient(commands); } + @DefaultComponent default ReactiveRedisClient lettuceReactiveCacheRedisClient(LettuceCommander commands) { return new LettuceReactiveRedisClient(commands); } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceReactiveRedisClient.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceReactiveRedisClient.java index 8125ad18d..1e113b649 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceReactiveRedisClient.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceReactiveRedisClient.java @@ -8,8 +8,10 @@ import reactor.core.publisher.Mono; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; -public final class LettuceReactiveRedisClient implements ReactiveRedisClient { +final class LettuceReactiveRedisClient implements ReactiveRedisClient { private final RedisStringReactiveCommands stringCommands; private final RedisServerReactiveCommands serverCommands; @@ -26,28 +28,43 @@ public Mono get(byte[] key) { return stringCommands.get(key); } + @Override + public Mono> get(byte[][] keys) { + return stringCommands.mget(keys).collect(HashMap::new, ((collector, keyValue) -> collector.put(keyValue.getKey(), keyValue.getValue()))); + } + @Override public Mono getExpire(byte[] key, long expireAfterMillis) { return stringCommands.getex(key, GetExArgs.Builder.ex(Duration.ofMillis(expireAfterMillis))); } @Override - public Mono set(byte[] key, byte[] value) { - return stringCommands.set(key, value).then(); + public Mono> getExpire(byte[][] key, long expireAfterMillis) { + throw new UnsupportedOperationException(); + } + + @Override + public Mono set(byte[] key, byte[] value) { + return stringCommands.set(key, value).then(Mono.just(true)); + } + + @Override + public Mono setExpire(byte[] key, byte[] value, long expireAfterMillis) { + return stringCommands.psetex(key, expireAfterMillis, value).then(Mono.just(true)); } @Override - public Mono setExpire(byte[] key, byte[] value, long expireAfterMillis) { - return stringCommands.psetex(key, expireAfterMillis, value).then(); + public Mono del(byte[] key) { + return keyCommands.del(key); } @Override - public Mono del(byte[] key) { - return keyCommands.del(key).then(); + public Mono del(byte[][] keys) { + return keyCommands.del(keys); } @Override - public Mono flushAll() { - return serverCommands.flushall(FlushMode.SYNC).then(); + public Mono flushAll() { + return serverCommands.flushall(FlushMode.SYNC).then(Mono.just(true)); } } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceSyncRedisClient.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceSyncRedisClient.java index 7c1922da1..1f4d4048d 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceSyncRedisClient.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/LettuceSyncRedisClient.java @@ -2,13 +2,17 @@ import io.lettuce.core.FlushMode; import io.lettuce.core.GetExArgs; +import io.lettuce.core.KeyValue; +import io.lettuce.core.Value; import io.lettuce.core.api.sync.RedisKeyCommands; import io.lettuce.core.api.sync.RedisServerCommands; import io.lettuce.core.api.sync.RedisStringCommands; import java.time.Duration; +import java.util.Map; +import java.util.stream.Collectors; -public final class LettuceSyncRedisClient implements SyncRedisClient { +final class LettuceSyncRedisClient implements SyncRedisClient { private final RedisStringCommands stringCommands; private final RedisServerCommands serverCommands; @@ -25,11 +29,22 @@ public byte[] get(byte[] key) { return stringCommands.get(key); } + @Override + public Map get(byte[][] keys) { + return stringCommands.mget(keys).stream() + .collect(Collectors.toMap(KeyValue::getKey, Value::getValue)); + } + @Override public byte[] getExpire(byte[] key, long expireAfterMillis) { return stringCommands.getex(key, GetExArgs.Builder.ex(Duration.ofMillis(expireAfterMillis))); } + @Override + public Map getExpire(byte[][] keys, long expireAfterMillis) { + throw new UnsupportedOperationException(); + } + @Override public void set(byte[] key, byte[] value) { stringCommands.set(key, value); @@ -41,8 +56,13 @@ public void setExpire(byte[] key, byte[] value, long expireAfterMillis) { } @Override - public void del(byte[] key) { - keyCommands.del(key); + public long del(byte[] key) { + return keyCommands.del(key); + } + + @Override + public long del(byte[][] keys) { + return keyCommands.del(keys); } @Override diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/ReactiveRedisClient.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/ReactiveRedisClient.java index 334472885..6ef6f22d1 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/ReactiveRedisClient.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/ReactiveRedisClient.java @@ -2,17 +2,25 @@ import reactor.core.publisher.Mono; +import java.util.Map; + public interface ReactiveRedisClient { Mono get(byte[] key); + Mono> get(byte[][] keys); + Mono getExpire(byte[] key, long expireAfterMillis); - Mono set(byte[] key, byte[] value); + Mono> getExpire(byte[][] key, long expireAfterMillis); + + Mono set(byte[] key, byte[] value); + + Mono setExpire(byte[] key, byte[] value, long expireAfterMillis); - Mono setExpire(byte[] key, byte[] value, long expireAfterMillis); + Mono del(byte[] key); - Mono del(byte[] key); + Mono del(byte[][] keys); - Mono flushAll(); + Mono flushAll(); } diff --git a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/SyncRedisClient.java b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/SyncRedisClient.java index 25b6e42a5..0b0a54548 100644 --- a/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/SyncRedisClient.java +++ b/cache/cache-redis/src/main/java/ru/tinkoff/kora/cache/redis/client/SyncRedisClient.java @@ -1,16 +1,24 @@ package ru.tinkoff.kora.cache.redis.client; +import java.util.Map; + public interface SyncRedisClient { byte[] get(byte[] key); + Map get(byte[][] keys); + byte[] getExpire(byte[] key, long expireAfterMillis); + Map getExpire(byte[][] keys, long expireAfterMillis); + void set(byte[] key, byte[] value); void setExpire(byte[] key, byte[] value, long expireAfterMillis); - void del(byte[] key); + long del(byte[] key); + + long del(byte[][] keys); void flushAll(); } diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/CacheRunner.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/CacheRunner.java index 3dca3f55a..54f9d291e 100644 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/CacheRunner.java +++ b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/CacheRunner.java @@ -1,46 +1,40 @@ package ru.tinkoff.kora.cache.redis; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Assertions; -import ru.tinkoff.kora.annotation.processor.common.TestUtils; -import ru.tinkoff.kora.aop.annotation.processor.AopAnnotationProcessor; -import ru.tinkoff.kora.application.graph.ApplicationGraphDraw; -import ru.tinkoff.kora.cache.annotation.processor.CacheKeyAnnotationProcessor; -import ru.tinkoff.kora.cache.redis.testdata.AppWithConfig; -import ru.tinkoff.kora.cache.redis.testdata.Box; -import ru.tinkoff.kora.cache.redis.testdata.CacheableTargetMono; -import ru.tinkoff.kora.cache.redis.testdata.CacheableTargetSync; -import ru.tinkoff.kora.config.annotation.processor.processor.ConfigSourceAnnotationProcessor; -import ru.tinkoff.kora.json.annotation.processor.JsonAnnotationProcessor; -import ru.tinkoff.kora.kora.app.annotation.processor.KoraAppProcessor; +import ru.tinkoff.kora.cache.redis.client.LettuceClientConfig; +import ru.tinkoff.kora.cache.redis.testdata.DummyCache; +import ru.tinkoff.kora.test.redis.RedisParams; -import java.lang.reflect.Constructor; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; +import java.time.Duration; -public abstract class CacheRunner extends Assertions { +abstract class CacheRunner extends Assertions implements RedisCacheModule { - public static URI redisUri; + public static RedisCacheConfig getConfig() { + return new RedisCacheConfig() { + @Nullable + @Override + public Duration expireAfterWrite() { + return null; + } - protected static ApplicationGraphDraw createGraphDraw() throws Exception { - return createGraphDraw(AppWithConfig.class, CacheableTargetSync.class, CacheableTargetMono.class, Box.class); + @Nullable + @Override + public Duration expireAfterAccess() { + return null; + } + }; } - @SuppressWarnings("unchecked") - protected static ApplicationGraphDraw createGraphDraw(Class app, Class... targetClasses) throws Exception { - try { - final List> classes = new ArrayList<>(List.of(targetClasses)); - classes.add(app); - var classLoader = TestUtils.annotationProcess(classes, new KoraAppProcessor(), new JsonAnnotationProcessor(), new AopAnnotationProcessor(), new CacheKeyAnnotationProcessor(), new ConfigSourceAnnotationProcessor()); - var clazz = classLoader.loadClass(app.getName() + "Graph"); - var constructors = (Constructor>[]) clazz.getConstructors(); - return constructors[0].newInstance().get(); - } catch (Exception e) { - if (e.getCause() != null) { - throw (Exception) e.getCause(); - } - throw e; - } + protected DummyCache createCache(RedisParams redisParams) { + var lettuceClientFactory = lettuceClientFactory(); + var lettuceClientConfig = new LettuceClientConfig(redisParams.uri().toString(), null, null, null, null, null, null); + var lettuceCommander = lettuceCommander(lettuceClientFactory.build(lettuceClientConfig)); + lettuceCommander.init().block(Duration.ofMinutes(1)); + + var syncRedisClient = lettuceCacheRedisClient(lettuceCommander); + var reactiveRedisClient = lettuceReactiveCacheRedisClient(lettuceCommander); + return new DummyCache(getConfig(), syncRedisClient, reactiveRedisClient, redisCacheTelemetry(null, null), + stringRedisKeyMapper(), stringRedisValueMapper()); } } diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheAopTests.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheAopTests.java deleted file mode 100644 index 2c5e60e45..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheAopTests.java +++ /dev/null @@ -1,187 +0,0 @@ -package ru.tinkoff.kora.cache.redis; - -import io.lettuce.core.FlushMode; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; -import ru.tinkoff.kora.cache.redis.testdata.Box; -import ru.tinkoff.kora.cache.redis.testdata.CacheableMockLifecycle; -import ru.tinkoff.kora.cache.redis.testdata.CacheableTargetMono; -import ru.tinkoff.kora.test.redis.RedisParams; -import ru.tinkoff.kora.test.redis.RedisTestContainer; - -import java.math.BigDecimal; -import java.time.Duration; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@RedisTestContainer -class MonoCacheAopTests extends CacheRunner { - - private SyncRedisClient syncRedisClient = null; - private CacheableTargetMono service = null; - - private CacheableTargetMono getService() { - if (service != null) { - return service; - } - - try { - var graphDraw = createGraphDraw(); - var graph = graphDraw.init().block(); - var values = graphDraw.getNodes() - .stream() - .map(graph::get) - .toList(); - - syncRedisClient = values.stream() - .filter(a1 -> a1 instanceof SyncRedisClient) - .map(a1 -> ((SyncRedisClient) a1)) - .findFirst().orElseThrow(); - - service = values.stream() - .filter(a -> a instanceof CacheableMockLifecycle) - .map(a -> ((CacheableMockLifecycle) a).mono()) - .findFirst().orElseThrow(); - return service; - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @BeforeEach - void setupRedis(RedisParams redisParams) { - CacheRunner.redisUri = redisParams.uri(); - redisParams.execute(cmd -> cmd.flushall(FlushMode.SYNC)); - } - - @Test - void getFromCacheWhenWasCacheEmpty() { - // given - final CacheableTargetMono service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box notCached = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - service.number = "2"; - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotNull(fromCache); - assertEquals(notCached, fromCache); - assertNotEquals("2", fromCache.number()); - - // cleanup - service.evictAll().block(Duration.ofMinutes(1)); - } - - @Test - void getFromCacheWhenCacheFilled() { - // given - final CacheableTargetMono service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.number = "2"; - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertEquals(cached, fromCache); - - // cleanup - service.evictAll().block(Duration.ofMinutes(1)); - } - - @Test - void getFromCacheWrongKeyWhenCacheFilled() { - // given - final CacheableTargetMono service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.number = "2"; - - // then - final Box fromCache = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - assertEquals(service.number, fromCache.number()); - - // cleanup - service.evictAll().block(Duration.ofMinutes(1)); - } - - @Test - void getFromCacheWhenCacheFilledOtherKey() { - // given - final CacheableTargetMono service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - service.number = "2"; - final Box initial = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, initial); - - // then - final Box fromCache = service.getValue("2", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - assertEquals(initial, fromCache); - - // cleanup - service.evictAll().block(Duration.ofMinutes(1)); - } - - @Test - void getFromCacheWhenCacheInvalidate() { - // given - final CacheableTargetMono service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.number = "2"; - service.evictValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - - // cleanup - service.evictAll().block(Duration.ofMinutes(1)); - } - - @Test - void getFromCacheWhenCacheInvalidateAll() { - // given - final CacheableTargetMono service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1").block(Duration.ofMinutes(1)); - assertEquals(initial, cached); - service.number = "2"; - service.evictAll().block(Duration.ofMinutes(1)); - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO).block(Duration.ofMinutes(1)); - assertNotEquals(cached, fromCache); - - // cleanup - service.evictAll().block(Duration.ofMinutes(1)); - } -} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheTests.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheTests.java new file mode 100644 index 000000000..4338a2ba0 --- /dev/null +++ b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/MonoCacheTests.java @@ -0,0 +1,91 @@ +package ru.tinkoff.kora.cache.redis; + +import io.lettuce.core.FlushMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.cache.redis.testdata.DummyCache; +import ru.tinkoff.kora.test.redis.RedisParams; +import ru.tinkoff.kora.test.redis.RedisTestContainer; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@RedisTestContainer +class MonoCacheTests extends CacheRunner { + + private DummyCache cache = null; + + @BeforeEach + void setup(RedisParams redisParams) { + redisParams.execute(cmd -> cmd.flushall(FlushMode.SYNC)); + if (cache == null) { + cache = createCache(redisParams); + } + } + + @Test + void getWhenCacheEmpty() { + // given + var key = "1"; + + // when + assertNull(cache.getAsync(key).block()); + } + + @Test + void getWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.putAsync(key, value).block(); + + // then + final String fromCache = cache.getAsync(key).block(); + assertEquals(value, fromCache); + } + + @Test + void getWrongKeyWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.putAsync(key, value).block(); + + // then + final String fromCache = cache.getAsync("2").block(); + assertNull(fromCache); + } + + @Test + void getWhenCacheInvalidate() { + // given + var key = "1"; + var value = "1"; + cache.putAsync(key, value).block(); + + // when + cache.invalidateAsync(key).block(); + + // then + final String fromCache = cache.getAsync(key).block(); + assertNull(fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidateAll() { + // given + var key = "1"; + var value = "1"; + cache.putAsync(key, value).block(); + + // when + cache.invalidateAllAsync().block(); + + // then + final String fromCache = cache.getAsync(key).block(); + assertNull(fromCache); + } +} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheAopTests.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheAopTests.java deleted file mode 100644 index 1cdf0706c..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheAopTests.java +++ /dev/null @@ -1,185 +0,0 @@ -package ru.tinkoff.kora.cache.redis; - -import io.lettuce.core.FlushMode; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; -import ru.tinkoff.kora.cache.redis.testdata.Box; -import ru.tinkoff.kora.cache.redis.testdata.CacheableMockLifecycle; -import ru.tinkoff.kora.cache.redis.testdata.CacheableTargetSync; -import ru.tinkoff.kora.test.redis.RedisParams; -import ru.tinkoff.kora.test.redis.RedisTestContainer; - -import java.math.BigDecimal; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@RedisTestContainer -class SyncCacheAopTests extends CacheRunner { - - private SyncRedisClient syncRedisClient = null; - private CacheableTargetSync service = null; - - private CacheableTargetSync getService() { - if (service != null) { - return service; - } - - try { - var graphDraw = createGraphDraw(); - var graph = graphDraw.init().block(); - var values = graphDraw.getNodes() - .stream() - .map(graph::get) - .toList(); - - syncRedisClient = values.stream() - .filter(a1 -> a1 instanceof SyncRedisClient) - .map(a1 -> ((SyncRedisClient) a1)) - .findFirst().orElseThrow(); - - service = values.stream() - .filter(a -> a instanceof CacheableMockLifecycle) - .map(a -> ((CacheableMockLifecycle) a).sync()) - .findFirst().orElseThrow(); - return service; - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - - @BeforeEach - void setupRedis(RedisParams redisParams) { - CacheRunner.redisUri = redisParams.uri(); - redisParams.execute(cmd -> cmd.flushall(FlushMode.SYNC)); - } - - @Test - void getFromCacheWhenWasCacheEmpty() { - // given - final CacheableTargetSync service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box notCached = service.getValue("1", BigDecimal.ZERO); - service.number = "2"; - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO); - assertEquals(notCached, fromCache); - assertNotEquals("2", fromCache.number()); - - // cleanup - service.evictAll(); - } - - @Test - void getFromCacheWhenCacheFilled() { - // given - final CacheableTargetSync service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.number = "2"; - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO); - assertEquals(cached, fromCache); - - // cleanup - service.evictAll(); - } - - @Test - void getFromCacheWrongKeyWhenCacheFilled() { - // given - final CacheableTargetSync service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.number = "2"; - - // then - final Box fromCache = service.getValue("2", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - assertEquals(service.number, fromCache.number()); - - // cleanup - service.evictAll(); - } - - @Test - void getFromCacheWhenCacheFilledOtherKey() { - // given - final CacheableTargetSync service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1"); - service.number = "2"; - final Box initial = service.getValue("2", BigDecimal.ZERO); - assertNotEquals(cached, initial); - - // then - final Box fromCache = service.getValue("2", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - assertEquals(initial, fromCache); - - // cleanup - service.evictAll(); - } - - @Test - void getFromCacheWhenCacheInvalidate() { - // given - final CacheableTargetSync service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.number = "2"; - service.evictValue("1", BigDecimal.ZERO); - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - - // cleanup - service.evictAll(); - } - - @Test - void getFromCacheWhenCacheInvalidateAll() { - // given - final CacheableTargetSync service = getService(); - service.number = "1"; - assertNotNull(service); - - // when - final Box initial = service.getValue("1", BigDecimal.ZERO); - final Box cached = service.putValue(BigDecimal.ZERO, "5", "1"); - assertEquals(initial, cached); - service.number = "2"; - service.evictAll(); - - // then - final Box fromCache = service.getValue("1", BigDecimal.ZERO); - assertNotEquals(cached, fromCache); - - // cleanup - service.evictAll(); - } -} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheTests.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheTests.java new file mode 100644 index 000000000..d0e268d06 --- /dev/null +++ b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/SyncCacheTests.java @@ -0,0 +1,91 @@ +package ru.tinkoff.kora.cache.redis; + +import io.lettuce.core.FlushMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import ru.tinkoff.kora.cache.redis.testdata.DummyCache; +import ru.tinkoff.kora.test.redis.RedisParams; +import ru.tinkoff.kora.test.redis.RedisTestContainer; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@RedisTestContainer +class SyncCacheTests extends CacheRunner { + + private DummyCache cache = null; + + @BeforeEach + void setup(RedisParams redisParams) { + redisParams.execute(cmd -> cmd.flushall(FlushMode.SYNC)); + if (cache == null) { + cache = createCache(redisParams); + } + } + + @Test + void getWhenCacheEmpty() { + // given + var key = "1"; + + // when + assertNull(cache.get(key)); + } + + @Test + void getWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.put(key, value); + + // then + final String fromCache = cache.get(key); + assertEquals(value, fromCache); + } + + @Test + void getWrongKeyWhenCacheFilled() { + // given + var key = "1"; + var value = "1"; + + // when + cache.put(key, value); + + // then + final String fromCache = cache.get("2"); + assertNull(fromCache); + } + + @Test + void getWhenCacheInvalidate() { + // given + var key = "1"; + var value = "1"; + cache.put(key, value); + + // when + cache.invalidate(key); + + // then + final String fromCache = cache.get(key); + assertNull(fromCache); + } + + @Test + void getFromCacheWhenCacheInvalidateAll() { + // given + var key = "1"; + var value = "1"; + cache.put(key, value); + + // when + cache.invalidateAll(); + + // then + final String fromCache = cache.get(key); + assertNull(fromCache); + } +} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/AppWithConfig.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/AppWithConfig.java deleted file mode 100644 index 5b8c1e020..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/AppWithConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package ru.tinkoff.kora.cache.redis.testdata; - -import ru.tinkoff.kora.cache.redis.CacheRunner; -import ru.tinkoff.kora.cache.redis.DefaultRedisCacheModule; -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; -import ru.tinkoff.kora.common.KoraApp; -import ru.tinkoff.kora.common.annotation.Root; -import ru.tinkoff.kora.config.common.Config; -import ru.tinkoff.kora.config.common.DefaultConfigExtractorsModule; -import ru.tinkoff.kora.config.common.factory.MapConfigFactory; - -import java.util.Map; - -@KoraApp -public interface AppWithConfig extends DefaultConfigExtractorsModule, DefaultRedisCacheModule { - - default Config config() { - return MapConfigFactory.fromMap(Map.of( - "lettuce", Map.of( - "uri", CacheRunner.redisUri.toString(), - "timeout", "15s" - ) - )); - } - - @Root - default CacheableMockLifecycle object(CacheableTargetSync cacheableTargetSync, CacheableTargetMono cacheableTargetMono, SyncRedisClient cacheClient) { - return new CacheableMockLifecycle(cacheableTargetMono, cacheableTargetSync, cacheClient); - } -} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/Box.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/Box.java deleted file mode 100644 index c5d70feb8..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/Box.java +++ /dev/null @@ -1,5 +0,0 @@ -package ru.tinkoff.kora.cache.redis.testdata; - -import java.math.BigDecimal; - -public record Box(String number, BigDecimal code) {} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableMockLifecycle.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableMockLifecycle.java deleted file mode 100644 index 3bfd9c2bd..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableMockLifecycle.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.tinkoff.kora.cache.redis.testdata; - -import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; - -public record CacheableMockLifecycle(CacheableTargetMono mono, CacheableTargetSync sync, SyncRedisClient client) { - -} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetMono.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetMono.java deleted file mode 100644 index f76d092f5..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetMono.java +++ /dev/null @@ -1,36 +0,0 @@ -package ru.tinkoff.kora.cache.redis.testdata; - -import reactor.core.publisher.Mono; -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.redis.RedisCacheManager; -import ru.tinkoff.kora.common.Component; - -import java.math.BigDecimal; - -@Component -public class CacheableTargetMono { - - public String number = "1"; - - @Cacheable(name = "mono_cache", tags = RedisCacheManager.class) - public Mono getValue(String arg1, BigDecimal arg2) { - return Mono.just(new Box(number, BigDecimal.TEN)); - } - - @CachePut(name = "mono_cache", tags = RedisCacheManager.class, parameters = {"arg1", "arg2"}) - public Mono putValue(BigDecimal arg2, String arg3, String arg1) { - return Mono.just(new Box(number, BigDecimal.TEN)); - } - - @CacheInvalidate(name = "mono_cache", tags = RedisCacheManager.class) - public Mono evictValue(String arg1, BigDecimal arg2) { - return Mono.empty(); - } - - @CacheInvalidate(name = "mono_cache", tags = RedisCacheManager.class, invalidateAll = true) - public Mono evictAll() { - return Mono.empty(); - } -} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetSync.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetSync.java deleted file mode 100644 index 35a5760ec..000000000 --- a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/CacheableTargetSync.java +++ /dev/null @@ -1,35 +0,0 @@ -package ru.tinkoff.kora.cache.redis.testdata; - -import ru.tinkoff.kora.cache.annotation.CacheInvalidate; -import ru.tinkoff.kora.cache.annotation.CachePut; -import ru.tinkoff.kora.cache.annotation.Cacheable; -import ru.tinkoff.kora.cache.redis.RedisCacheManager; -import ru.tinkoff.kora.common.Component; - -import java.math.BigDecimal; - -@Component -public class CacheableTargetSync { - - public String number = "1"; - - @Cacheable(name = "sync_cache", tags = RedisCacheManager.class) - public Box getValue(String arg1, BigDecimal arg2) { - return new Box(number, BigDecimal.TEN); - } - - @CachePut(name = "sync_cache", tags = RedisCacheManager.class, parameters = {"arg1", "arg2"}) - public Box putValue(BigDecimal arg2, String arg3, String arg1) { - return new Box(number, BigDecimal.TEN); - } - - @CacheInvalidate(name = "sync_cache", tags = RedisCacheManager.class) - public void evictValue(String arg1, BigDecimal arg2) { - - } - - @CacheInvalidate(name = "sync_cache", tags = RedisCacheManager.class, invalidateAll = true) - public void evictAll() { - - } -} diff --git a/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/DummyCache.java b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/DummyCache.java new file mode 100644 index 000000000..5c073f249 --- /dev/null +++ b/cache/cache-redis/src/test/java/ru/tinkoff/kora/cache/redis/testdata/DummyCache.java @@ -0,0 +1,17 @@ +package ru.tinkoff.kora.cache.redis.testdata; + +import ru.tinkoff.kora.cache.redis.*; +import ru.tinkoff.kora.cache.redis.client.ReactiveRedisClient; +import ru.tinkoff.kora.cache.redis.client.SyncRedisClient; + +public final class DummyCache extends AbstractRedisCache { + + public DummyCache(RedisCacheConfig config, + SyncRedisClient syncClient, + ReactiveRedisClient reactiveClient, + RedisCacheTelemetry telemetry, + RedisCacheKeyMapper keyMapper, + RedisCacheValueMapper valueMapper) { + super("dummy", config, syncClient, reactiveClient, telemetry, keyMapper, valueMapper); + } +} diff --git a/cache/cache-symbol-processor/build.gradle b/cache/cache-symbol-processor/build.gradle index 5d41dcfeb..950a03bb3 100644 --- a/cache/cache-symbol-processor/build.gradle +++ b/cache/cache-symbol-processor/build.gradle @@ -7,14 +7,15 @@ apply from: "${project.rootDir}/kotlin-plugin.gradle" dependencies { implementation project(":aop:aop-symbol-processor") - implementation project(":cache:cache-common") implementation libs.ksp.api implementation libs.kotlin.reflect implementation libs.kotlinpoet implementation libs.kotlinpoet.ksp + testImplementation libs.prometheus.collector.caffeine testImplementation project(":internal:test-logging") + testImplementation project(":cache:cache-caffeine") testImplementation testFixtures(project(":symbol-processor-common")) testImplementation(libs.kotlin.stdlib.lib) testImplementation(libs.kotlin.coroutines.reactor) @@ -34,4 +35,4 @@ kotlin { } } -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle" diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessor.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessor.kt deleted file mode 100644 index 20f4b5480..000000000 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessor.kt +++ /dev/null @@ -1,137 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor - -import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.processing.Resolver -import com.google.devtools.ksp.processing.SymbolProcessorEnvironment -import com.google.devtools.ksp.symbol.KSAnnotated -import com.google.devtools.ksp.validate -import com.squareup.kotlinpoet.* -import com.squareup.kotlinpoet.ksp.toClassName -import com.squareup.kotlinpoet.ksp.writeTo -import ru.tinkoff.kora.cache.CacheKey -import ru.tinkoff.kora.cache.annotation.* -import ru.tinkoff.kora.cache.symbol.processor.MethodUtils.Companion.getParameters -import ru.tinkoff.kora.ksp.common.BaseSymbolProcessor -import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlow -import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlux -import ru.tinkoff.kora.ksp.common.FunctionUtils.isFuture -import ru.tinkoff.kora.ksp.common.FunctionUtils.isMono -import ru.tinkoff.kora.ksp.common.FunctionUtils.isPublisher -import ru.tinkoff.kora.ksp.common.FunctionUtils.isVoid -import ru.tinkoff.kora.ksp.common.KspCommonUtils.generated -import ru.tinkoff.kora.ksp.common.exception.ProcessingError -import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException -import ru.tinkoff.kora.ksp.common.visitFunction -import java.io.IOException - -@KspExperimental -class CacheKeySymbolProcessor( - private val environment: SymbolProcessorEnvironment, - private val generatedCacheKeys: HashSet -) : BaseSymbolProcessor(environment) { - - private val cacheAnnotations = setOf( - Cacheable::class, Cacheables::class, - CachePut::class, CachePuts::class, - CacheInvalidate::class, CacheInvalidates::class - ) - - override fun processRound(resolver: Resolver): List { - val symbols = resolver.getSymbolsWithAnnotation(Cacheable::class.qualifiedName!!) - .plus(resolver.getSymbolsWithAnnotation(Cacheables::class.qualifiedName!!)) - .plus(resolver.getSymbolsWithAnnotation(CachePut::class.qualifiedName!!)) - .plus(resolver.getSymbolsWithAnnotation(CachePuts::class.qualifiedName!!)) - .plus(resolver.getSymbolsWithAnnotation(CacheInvalidate::class.qualifiedName!!)) - .plus(resolver.getSymbolsWithAnnotation(CacheInvalidates::class.qualifiedName!!)) - .toList() - - val symbolsToProcess = symbols.filter { it.validate() } - symbolsToProcess.forEach { - it.visitFunction { method -> - val cacheAnnotations = method.annotations - .filter { a -> - val canonicalName = a.annotationType.resolve().toClassName().canonicalName - cacheAnnotations.any { an -> an.qualifiedName == canonicalName } - }.toList() - - if (cacheAnnotations.isNotEmpty()) { - try { - val annotationNames = cacheAnnotations.map { a -> a.shortName.getShortName() }.toList() - val operation = CacheOperationManager.getCacheOperation(method, resolver) - - if (operation.meta.type == CacheMeta.Type.GET || operation.meta.type == CacheMeta.Type.PUT) { - if (method.isVoid()) { - throw IllegalArgumentException("$annotationNames annotation can't return Void type, but was for ${operation.meta.origin}") - } - - if (method.isMono()) { - throw IllegalArgumentException("$annotationNames annotation doesn't support return type ${method.returnType} in ${operation.meta.origin}") - } else if (method.isFuture()) { - throw IllegalArgumentException("$annotationNames annotation doesn't support return type ${method.returnType} in ${operation.meta.origin}") - } else if (method.isFlux()) { - throw IllegalArgumentException("$annotationNames annotation doesn't support return type ${method.returnType} in ${operation.meta.origin}") - } else if (method.isPublisher()) { - throw IllegalArgumentException("$annotationNames annotation doesn't support return type ${method.returnType} in ${operation.meta.origin}") - } else if (method.isFlow()) { - throw IllegalArgumentException("$annotationNames annotation doesn't support return type ${method.returnType} in ${operation.meta.origin}") - } - } - - if (!generatedCacheKeys.contains(operation.key.canonicalName())) { - val parameters = method.getParameters(operation.meta.parameters) - val methodParameters = operation.meta.getParametersNames(method).joinToString(",") - val toStringParameters = parameters - .map { a -> "$" + a.name!!.getShortName() } - .joinToString("-", "\"", "\"") - - var keyBuilder = TypeSpec.classBuilder(operation.key.simpleName) - .addSuperinterface(CacheKey::class) - .addModifiers(KModifier.DATA) - .generated(this::class) - .addFunction( - FunSpec.builder("toString") - .addModifiers(KModifier.OVERRIDE) - .addCode(CodeBlock.of("return %L", toStringParameters)) - .build() - ) - .addFunction( - FunSpec.builder("values") - .addModifiers(KModifier.OVERRIDE) - .addCode(CodeBlock.of("return mutableListOf(%L)", methodParameters)) - .build() - ) - - var constructorBuilder = FunSpec.constructorBuilder() - for (parameter in parameters) { - val paramName = parameter.name!!.getShortName() - val typeName = parameter.type.resolve().toClassName().copy(nullable = true) - constructorBuilder = constructorBuilder - .addParameter(paramName, typeName) - - keyBuilder = keyBuilder - .addProperty( - PropertySpec.builder(paramName, typeName) - .initializer(paramName) - .build() - ) - } - keyBuilder.primaryConstructor(constructorBuilder.build()) - - val fileSpec = FileSpec.builder(operation.key.packageName, operation.key.simpleName) - .addType(keyBuilder.build()) - .build() - - fileSpec.writeTo(codeGenerator = environment.codeGenerator, aggregating = false) - generatedCacheKeys.add(operation.key.canonicalName()) - } - } catch (e: IOException) { - throw ProcessingErrorException(ProcessingError(e.message.toString(), it)); - } - } - } - } - - return symbols.filterNot { it.validate() }.toList() - } -} - diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheMeta.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheMeta.kt deleted file mode 100644 index d41de9b50..000000000 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheMeta.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor - -import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.symbol.KSFunctionDeclaration -import ru.tinkoff.kora.cache.symbol.processor.MethodUtils.Companion.getParameters - -@KspExperimental -data class CacheMeta( - val type: Type, - val managers: List, - val parameters: List, - val origin: Origin -) { - - enum class Type { - GET, PUT, EVICT, EVICT_ALL - } - - data class Manager(val name: String, val tags: List) - - data class Origin(val className: String, val methodName: String) { - override fun toString(): String = "[class=$className, method=$methodName]" - } - - fun getParametersNames(method: KSFunctionDeclaration): List { - return method.getParameters(parameters).asSequence() - .map { it.name!!.getShortName() } - .toList() - } -} diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperation.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperation.kt index 9e904e0a3..064880013 100644 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperation.kt +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperation.kt @@ -1,13 +1,29 @@ package ru.tinkoff.kora.cache.symbol.processor import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSValueParameter +import ru.tinkoff.kora.cache.symbol.processor.MethodUtils.Companion.getParameters @KspExperimental -data class CacheOperation(val meta: CacheMeta, val key: Key, val value: Value) { +data class CacheOperation( + val type: Type, + val cacheImplementations: List, + val parameters: List, + val origin: Origin +) { - data class Key(val packageName: String, val simpleName: String) { - fun canonicalName(): String = if (packageName.isEmpty()) simpleName else "$packageName.$simpleName" + enum class Type { + GET, PUT, EVICT, EVICT_ALL } - data class Value(val canonicalName: String?) + data class Origin(val className: String, val methodName: String) { + override fun toString(): String = "[class=$className, method=$methodName]" + } + + fun getParametersNames(method: KSFunctionDeclaration): List { + return method.getParameters(parameters.asSequence().map { p -> p.name!!.getShortName() }.toList()).asSequence() + .map { it.name!!.getShortName() } + .toList() + } } diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationManager.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationManager.kt deleted file mode 100644 index 3017d5fdf..000000000 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationManager.kt +++ /dev/null @@ -1,249 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor - -import com.google.devtools.ksp.KspExperimental -import com.google.devtools.ksp.getAnnotationsByType -import com.google.devtools.ksp.processing.Resolver -import com.google.devtools.ksp.symbol.KSFunctionDeclaration -import com.squareup.kotlinpoet.ksp.toClassName -import ru.tinkoff.kora.cache.annotation.* -import ru.tinkoff.kora.cache.symbol.processor.MethodUtils.Companion.getParameters -import ru.tinkoff.kora.cache.symbol.processor.MethodUtils.Companion.getReturnValueCanonicalName -import ru.tinkoff.kora.ksp.common.FunctionUtils.isVoid -import ru.tinkoff.kora.ksp.common.exception.ProcessingError -import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException -import java.util.regex.Pattern -import javax.tools.Diagnostic - -@KspExperimental -class CacheOperationManager { - - private data class CacheSignature(val key: Type, val value: String?, val parameterTypes: List, val origin: CacheMeta.Origin) { - - data class Type(val packageName: String, val simpleName: String) - } - - companion object { - - private val ANNOTATIONS = setOf( - Cacheable::class.java, Cacheables::class.java, - CachePut::class.java, CachePuts::class.java, - CacheInvalidate::class.java, CacheInvalidates::class.java - ) - - private val NAME_PATTERN = Pattern.compile("^[a-zA-Z][0-9a-zA-Z_]*") - private val CACHE_NAME_TO_CACHE_KEY: HashMap = HashMap() - - fun reset() { - CACHE_NAME_TO_CACHE_KEY.clear() - } - - fun getCacheMeta(method: KSFunctionDeclaration): CacheMeta { - val className = method.parentDeclaration?.simpleName?.asString() ?: "" - val methodName = method.qualifiedName.toString() - val origin = CacheMeta.Origin(className, methodName) - - val cacheables = getCacheableAnnotations(method) - val puts = getCachePutAnnotations(method) - val invalidates = getCacheInvalidateAnnotations(method) - - val annotations = mutableSetOf() - cacheables.asSequence().forEach { a -> annotations.add(a.javaClass.canonicalName) } - puts.asSequence().forEach { a -> annotations.add(a.javaClass.canonicalName) } - invalidates.asSequence().forEach { a -> annotations.add(a.javaClass.canonicalName) } - - if (annotations.size > 1) { - throw ProcessingErrorException( - ProcessingError( - "Expected only one type of Cache annotations but was $annotations for $origin", - method, - Diagnostic.Kind.ERROR - ) - ) - } - - if (cacheables.isNotEmpty()) { - val managers = mutableListOf() - val managerParameters = mutableListOf>() - for (i in cacheables.indices) { - val annotation = cacheables[i] - val tags = listOf() - val manager = CacheMeta.Manager(annotation.name, tags) - managers.add(manager) - - val annotationParameters = annotation.parameters.toList() - for (managerParameter in managerParameters) { - if (managerParameter != annotationParameters) { - throw ProcessingErrorException( - ProcessingError( - "${annotation.javaClass} parameters mismatch for different annotations for $origin", - method, - Diagnostic.Kind.ERROR - ) - ) - } - } - managerParameters.add(annotationParameters) - } - - return CacheMeta(CacheMeta.Type.GET, managers, managerParameters[0], origin) - } else if (puts.isNotEmpty()) { - val managers = mutableListOf() - val managerParameters = mutableListOf>() - for (i in puts.indices) { - val annotation = puts[i] - val tags = listOf() - val manager = CacheMeta.Manager(annotation.name, tags) - managers.add(manager) - - val annotationParameters = annotation.parameters.toList() - for (managerParameter in managerParameters) { - if (managerParameter != annotationParameters) { - throw ProcessingErrorException( - ProcessingError( - "${annotation.javaClass} parameters mismatch for different annotations for $origin", - method, - Diagnostic.Kind.ERROR - ) - ) - } - } - managerParameters.add(annotationParameters) - } - - return CacheMeta(CacheMeta.Type.PUT, managers, managerParameters[0], origin) - } else if (invalidates.isNotEmpty()) { - val anyInvalidateAll = invalidates.any { a -> a.invalidateAll } - val allInvalidateAll = invalidates.all { a -> a.invalidateAll } - - if (anyInvalidateAll && !allInvalidateAll) { - throw ProcessingErrorException( - ProcessingError( - "${CacheInvalidate::class.java} not all annotations are marked 'invalidateAll' out of all for $origin", - method, - Diagnostic.Kind.ERROR - ) - ) - } - - val managers = mutableListOf() - val managerParameters = mutableListOf>() - for (i in invalidates.indices) { - val annotation = invalidates[i] - val tags = listOf() - val manager = CacheMeta.Manager(annotation.name, tags) - managers.add(manager) - - val annotationParameters = annotation.parameters.toList() - for (managerParameter in managerParameters) { - if (managerParameter != annotationParameters) { - throw ProcessingErrorException( - ProcessingError( - "${annotation.javaClass} parameters mismatch for different annotations for $origin", - method, - Diagnostic.Kind.ERROR - ) - ) - } - } - managerParameters.add(annotationParameters) - } - - val type = if (allInvalidateAll) CacheMeta.Type.EVICT_ALL else CacheMeta.Type.EVICT - return CacheMeta(type, managers, managerParameters[0], origin) - } - throw IllegalStateException("None of $ANNOTATIONS cache annotations found") - } - - fun getCacheOperation(method: KSFunctionDeclaration, env: Resolver): CacheOperation { - val meta = getCacheMeta(method) - val signature = getCacheSignature(meta, method, env) - return CacheOperation( - meta, - CacheOperation.Key(signature.key.packageName, signature.key.simpleName), - CacheOperation.Value(signature.value) - ) - } - - private fun getCacheSignature(meta: CacheMeta, method: KSFunctionDeclaration, resolver: Resolver): CacheSignature { - for (manager in meta.managers) { - if (!NAME_PATTERN.matcher(manager.name).matches()) { - throw IllegalArgumentException("Cache name for ${meta.origin} doesn't match pattern: $NAME_PATTERN") - } - } - - val parameterTypes = method.getParameters(meta.parameters) - .map { p -> p.type.resolve().toClassName().canonicalName } - .toList() - - val returnType = method.returnType - var cacheSignature = meta.managers.asSequence() - .map { manager -> CACHE_NAME_TO_CACHE_KEY[manager.name] } - .filterNotNull() - .firstOrNull() - - if (cacheSignature == null) { - val sigCacheName = meta.managers[0].name - val nonVoidReturnType = if (method.isVoid()) { - null - } else { - method.getReturnValueCanonicalName() - } - - val key = CacheSignature.Type(method.packageName.asString(), "_CacheKey__$sigCacheName") - val signature = CacheSignature(key, nonVoidReturnType, parameterTypes, meta.origin) - for (manager in meta.managers) { - CACHE_NAME_TO_CACHE_KEY[manager.name] = signature - } - cacheSignature = signature - } - - if (meta.type != CacheMeta.Type.EVICT_ALL && meta.type != CacheMeta.Type.EVICT) { - if (cacheSignature.parameterTypes != parameterTypes) { - throw IllegalStateException("Cache Key parameters from ${cacheSignature.origin} mismatch with ${meta.origin}, expected ${cacheSignature.parameterTypes} but was $parameterTypes") - } - - // Replace evict (void) operations previously saved - if (!method.isVoid()) { - val returnAsStr = method.getReturnValueCanonicalName() - if (cacheSignature.value != null && returnAsStr != cacheSignature.value) { - throw IllegalStateException("Cache Value type from ${cacheSignature.origin} mismatch with ${meta.origin}, expected ${cacheSignature.value} but was $returnType") - } else if (cacheSignature.value == null) { - val nonEvictSignature = CacheSignature(cacheSignature.key, returnAsStr, cacheSignature.parameterTypes, meta.origin) - for (manager in meta.managers) { - CACHE_NAME_TO_CACHE_KEY[manager.name] = nonEvictSignature - } - } - } - } - - return cacheSignature - } - - private fun getCacheableAnnotations(method: KSFunctionDeclaration): List { - val annotationAggregate = method.getAnnotationsByType(Cacheables::class).firstOrNull() - if (annotationAggregate != null) { - return annotationAggregate.value.toList() - } - - return method.getAnnotationsByType(Cacheable::class).toList(); - } - - private fun getCachePutAnnotations(method: KSFunctionDeclaration): List { - val annotationAggregate = method.getAnnotationsByType(CachePuts::class).firstOrNull() - if (annotationAggregate != null) { - return annotationAggregate.value.toList() - } - - return method.getAnnotationsByType(CachePut::class).toList() - } - - private fun getCacheInvalidateAnnotations(method: KSFunctionDeclaration): List { - val annotationAggregate = method.getAnnotationsByType(CacheInvalidates::class).firstOrNull() - if (annotationAggregate != null) { - return annotationAggregate.value.toList() - } - - return method.getAnnotationsByType(CacheInvalidate::class).toList() - } - } -} diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationUtils.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationUtils.kt new file mode 100644 index 000000000..369cedf18 --- /dev/null +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheOperationUtils.kt @@ -0,0 +1,191 @@ +package ru.tinkoff.kora.cache.symbol.processor + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.symbol.KSAnnotation +import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ksp.toClassName +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlow +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlux +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFuture +import ru.tinkoff.kora.ksp.common.FunctionUtils.isMono +import ru.tinkoff.kora.ksp.common.FunctionUtils.isPublisher +import ru.tinkoff.kora.ksp.common.FunctionUtils.isVoid +import ru.tinkoff.kora.ksp.common.exception.ProcessingError +import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException +import javax.tools.Diagnostic + +@KspExperimental +class CacheOperationUtils { + + companion object { + + private val ANNOTATION_CACHEABLE = ClassName("ru.tinkoff.kora.cache.annotation", "Cacheable") + private val ANNOTATION_CACHEABLES = ClassName("ru.tinkoff.kora.cache.annotation", "Cacheables") + private val ANNOTATION_CACHE_PUT = ClassName("ru.tinkoff.kora.cache.annotation", "CachePut") + private val ANNOTATION_CACHE_PUTS = ClassName("ru.tinkoff.kora.cache.annotation", "CachePuts") + private val ANNOTATION_CACHE_INVALIDATE = ClassName("ru.tinkoff.kora.cache.annotation", "CacheInvalidate") + private val ANNOTATION_CACHE_INVALIDATES = ClassName("ru.tinkoff.kora.cache.annotation", "CacheInvalidates") + + private val ANNOTATIONS = setOf( + ANNOTATION_CACHEABLE.canonicalName, ANNOTATION_CACHEABLES.canonicalName, + ANNOTATION_CACHE_PUT.canonicalName, ANNOTATION_CACHE_PUTS.canonicalName, + ANNOTATION_CACHE_INVALIDATE.canonicalName, ANNOTATION_CACHE_INVALIDATES.canonicalName + ) + + fun getCacheOperation(method: KSFunctionDeclaration): CacheOperation { + val className = method.parentDeclaration?.simpleName?.asString() ?: "" + val methodName = method.qualifiedName.toString() + val origin = CacheOperation.Origin(className, methodName) + + val cacheables = getCacheableAnnotations(method) + val puts = getCachePutAnnotations(method) + val invalidates = getCacheInvalidateAnnotations(method) + + val annotations = mutableSetOf() + cacheables.asSequence().forEach { a -> annotations.add(a.javaClass.canonicalName) } + puts.asSequence().forEach { a -> annotations.add(a.javaClass.canonicalName) } + invalidates.asSequence().forEach { a -> annotations.add(a.javaClass.canonicalName) } + + if (annotations.size > 1) { + throw ProcessingErrorException( + ProcessingError( + "Expected only one type of Cache annotations but was $annotations for $origin", + method, + Diagnostic.Kind.ERROR + ) + ) + } + + if (cacheables.isNotEmpty()) { + return getCacheOperation(method, CacheOperation.Type.GET, cacheables) + } else if (puts.isNotEmpty()) { + return getCacheOperation(method, CacheOperation.Type.PUT, puts) + } else if (invalidates.isNotEmpty()) { + val invalidateAlls = invalidates.asSequence() + .flatMap { a -> a.arguments.asSequence() } + .filter { a -> a.name!!.getShortName() == "invalidateAll" } + .map { a -> a.value as Boolean } + .toList() + + val anyInvalidateAll = invalidateAlls.any { v -> v } + val allInvalidateAll = invalidateAlls.all { v -> v } + + if (anyInvalidateAll && !allInvalidateAll) { + throw ProcessingErrorException( + ProcessingError( + "${ANNOTATION_CACHE_INVALIDATE.canonicalName} not all annotations are marked 'invalidateAll' out of all for " + origin, + method, + Diagnostic.Kind.ERROR, + ) + ) + } + + val type = if (allInvalidateAll) CacheOperation.Type.EVICT_ALL else CacheOperation.Type.EVICT + return getCacheOperation(method, type, invalidates) + } + + throw IllegalStateException("None of $ANNOTATIONS cache annotations found") + } + + private fun getCacheOperation(method: KSFunctionDeclaration, type: CacheOperation.Type, annotations: List): CacheOperation { + val className = method.parentDeclaration?.simpleName?.asString() ?: "" + val methodName = method.qualifiedName.toString() + val origin = CacheOperation.Origin(className, methodName) + + if (type == CacheOperation.Type.GET || type == CacheOperation.Type.PUT) { + if (method.isVoid()) { + throw IllegalArgumentException("@${annotations[0].shortName.getShortName()} annotation can't return Void type, but was for $origin") + } + } + + if (method.isMono() || method.isFlux() || method.isPublisher() || method.isFuture() || method.isFlow()) { + throw IllegalArgumentException("@${annotations[0].shortName.getShortName()} annotation doesn't support return type ${method.returnType} in $origin") + } + + val cacheImpls = mutableListOf() + val parameters = mutableListOf>() + for (i in annotations.indices) { + val annotation = annotations[i] + + val annotationParameters: List = annotation.arguments.filter { a -> a.name!!.getShortName() == "parameters" } + .map { it.value as List } + .firstOrNull { it.isNotEmpty() } + ?: method.parameters.asSequence().map { p -> p.name!!.getShortName() }.toList() + + for (parameter in parameters) { + if (parameter != annotationParameters) { + throw ProcessingErrorException( + ProcessingError( + "${annotation.javaClass} parameters mismatch for different annotations for $origin", + method, + Diagnostic.Kind.ERROR + ) + ) + } + } + + val cacheImpl = annotation.arguments.filter { a -> a.name!!.getShortName() == "value" } + .map { a -> a.value as KSType } + .first() + + parameters.add(annotationParameters) + cacheImpls.add(cacheImpl.toClassName().canonicalName) + } + + val parameterResult = parameters[0].asSequence() + .flatMap { param -> method.parameters.filter { p -> p.name!!.getShortName() == param } } + .toList() + + return CacheOperation(type, cacheImpls, parameterResult, origin) + } + + private fun getCacheableAnnotations(method: KSFunctionDeclaration): List { + method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHEABLES } + .map { a -> a.arguments[0] } + .map { arg -> arg.value } + .toList() + + val annotationAggregate = method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHEABLES } + .firstOrNull() + if (annotationAggregate != null) { + return emptyList() + } + + return method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHEABLE } + .toList() + } + + private fun getCachePutAnnotations(method: KSFunctionDeclaration): List { + val annotationAggregate = method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHE_PUTS } + .firstOrNull() + + if (annotationAggregate != null) { + return emptyList() + } + + return method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHE_PUT } + .toList() + } + + private fun getCacheInvalidateAnnotations(method: KSFunctionDeclaration): List { + val annotationAggregate = method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHE_INVALIDATES } + .firstOrNull() + + if (annotationAggregate != null) { + return emptyList() + } + + return method.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHE_INVALIDATE } + .toList() + } + } +} diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessor.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessor.kt new file mode 100644 index 000000000..9b8f6d613 --- /dev/null +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessor.kt @@ -0,0 +1,283 @@ +package ru.tinkoff.kora.cache.symbol.processor + +import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getClassDeclarationByName +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSTypeReference +import com.google.devtools.ksp.validate +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toTypeName +import com.squareup.kotlinpoet.ksp.toTypeVariableName +import com.squareup.kotlinpoet.ksp.writeTo +import ru.tinkoff.kora.common.Module +import ru.tinkoff.kora.common.Tag +import ru.tinkoff.kora.ksp.common.BaseSymbolProcessor +import ru.tinkoff.kora.ksp.common.CommonClassNames +import ru.tinkoff.kora.ksp.common.KspCommonUtils.toTypeName +import ru.tinkoff.kora.ksp.common.visitClass + +@KspExperimental +class CacheSymbolProcessor( + private val environment: SymbolProcessorEnvironment +) : BaseSymbolProcessor(environment) { + + private val ANNOTATION_CACHE = ClassName("ru.tinkoff.kora.cache.annotation", "Cache") + + private val CLASS_CONFIG = ClassName("ru.tinkoff.kora.config.common", "Config") + private val CLASS_CONFIG_EXTRACTOR = ClassName("ru.tinkoff.kora.config.common.extractor", "ConfigValueExtractor") + + private val CAFFEINE_TELEMETRY = ClassName("ru.tinkoff.kora.cache.caffeine", "CaffeineCacheTelemetry") + private val CAFFEINE_CACHE = ClassName("ru.tinkoff.kora.cache.caffeine", "CaffeineCache") + private val CAFFEINE_CACHE_FACTORY = ClassName("ru.tinkoff.kora.cache.caffeine", "CaffeineCacheFactory") + private val CAFFEINE_CACHE_CONFIG = ClassName("ru.tinkoff.kora.cache.caffeine", "CaffeineCacheConfig") + private val CAFFEINE_CACHE_IMPL = ClassName("ru.tinkoff.kora.cache.caffeine", "AbstractCaffeineCache") + + private val REDIS_TELEMETRY = ClassName("ru.tinkoff.kora.cache.redis", "RedisCacheTelemetry") + private val REDIS_CACHE = ClassName("ru.tinkoff.kora.cache.redis", "RedisCache") + private val REDIS_CACHE_IMPL = ClassName("ru.tinkoff.kora.cache.redis", "AbstractRedisCache") + private val REDIS_CACHE_CONFIG = ClassName("ru.tinkoff.kora.cache.redis", "RedisCacheConfig") + private val REDIS_CACHE_CLIENT_SYNC = ClassName("ru.tinkoff.kora.cache.redis", "SyncRedisClient") + private val REDIS_CACHE_CLIENT_REACTIVE = ClassName("ru.tinkoff.kora.cache.redis", "ReactiveRedisClient") + private val REDIS_CACHE_MAPPER_KEY = ClassName("ru.tinkoff.kora.cache.redis", "RedisCacheKeyMapper") + private val REDIS_CACHE_MAPPER_VALUE = ClassName("ru.tinkoff.kora.cache.redis", "RedisCacheValueMapper") + + override fun processRound(resolver: Resolver): List { + val symbols = resolver.getSymbolsWithAnnotation(ANNOTATION_CACHE.canonicalName).toList() + + val symbolsToProcess = symbols.filter { it.validate() } + symbolsToProcess.forEach { + it.visitClass { cacheContract -> + + require(cacheContract.classKind == ClassKind.INTERFACE) { "@Cache annotation is intended to be used on interfaces, but was: ${cacheContract.classKind}" } + + val cacheContractType = getCacheSuperType(cacheContract, resolver) + require(cacheContractType != null) { + ("@Cache is expected to be known super type " + + CAFFEINE_CACHE.canonicalName + + " or " + + REDIS_CACHE.canonicalName + + ", but was: " + cacheContract.superTypes.first()) + } + + val packageName = getPackage(cacheContract) + val cacheImplName = cacheContract.toClassName() + + val cacheImplBase = getCacheImplBase(cacheContract, cacheContractType, resolver) + val implSpec = TypeSpec.classBuilder(getCacheImpl(cacheContract)) + .addAnnotation( + AnnotationSpec.builder(CommonClassNames.generated) + .addMember(CodeBlock.of("%S", CacheSymbolProcessor::class.java.canonicalName)).build() + ) + .primaryConstructor(getCacheConstructor(cacheContract, cacheContractType)) + .addSuperclassConstructorParameter(getCacheSuperConstructorCall(cacheContract, cacheContractType)) + .superclass(cacheImplBase) + .addSuperinterface(cacheContract.toTypeName()) + .build() + + val fileImplSpec = FileSpec.builder(cacheContract.packageName.asString(), implSpec.name.toString()) + .addType(implSpec) + .build() + fileImplSpec.writeTo(codeGenerator = environment.codeGenerator, aggregating = false) + + val moduleSpec: TypeSpec = TypeSpec.interfaceBuilder(ClassName(packageName, "$${cacheImplName.simpleName}Module")) + .addAnnotation( + AnnotationSpec.builder(CommonClassNames.generated) + .addMember(CodeBlock.of("%S", CacheSymbolProcessor::class.java.canonicalName)).build() + ) + .addAnnotation(Module::class) + .addFunction(getCacheMethodImpl(cacheContract, cacheContractType)) + .addFunction(getCacheMethodConfig(cacheContract, cacheContractType, resolver)) + .build() + + val fileModuleSpec = FileSpec.builder(cacheContract.packageName.asString(), moduleSpec.name.toString()) + .addType(moduleSpec) + .build() + fileModuleSpec.writeTo(codeGenerator = environment.codeGenerator, aggregating = false) + } + } + + return symbols.filterNot { it.validate() }.toList() + } + + private fun getCacheSuperType(candidate: KSClassDeclaration, resolver: Resolver): KSTypeReference? { + val caffeineElement = resolver.getClassDeclarationByName(CAFFEINE_CACHE.canonicalName)?.asType(listOf()) + if (caffeineElement != null) { + val superType = candidate.superTypes.filter { t -> t.resolve().toClassName() == caffeineElement.toClassName() } + .firstOrNull() + + if (superType != null) { + return superType + } + } + + val redisElement = resolver.getClassDeclarationByName(REDIS_CACHE.canonicalName)?.asType(listOf()) + if (redisElement != null) { + val superType = candidate.superTypes.filter { t -> t.resolve().toClassName() == redisElement.toClassName() } + .firstOrNull() + + if (superType != null) { + return superType + } + } + + return null + } + + private fun getCacheImplBase(cacheContract: KSClassDeclaration, cacheType: KSTypeReference, resolver: Resolver): TypeName { + val resolved = cacheType.resolve() + return if (resolved.toClassName() == CAFFEINE_CACHE) { + resolver.getClassDeclarationByName(CAFFEINE_CACHE_IMPL.canonicalName)!!.asType(cacheType.resolve().arguments).toTypeName() + } else if (resolved.toClassName() == REDIS_CACHE) { + resolver.getClassDeclarationByName(REDIS_CACHE_IMPL.canonicalName)!!.asType(cacheType.resolve().arguments).toTypeName() + } else { + throw UnsupportedOperationException("Unknown implementation: " + cacheContract.toClassName()) + } + } + + private fun getCacheMethodConfig(cacheContract: KSClassDeclaration, cacheType: KSTypeReference, resolver: Resolver): FunSpec { + val configPath = cacheContract.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHE } + .flatMap { a -> a.arguments } + .filter { arg -> arg.name!!.getShortName() == "value" } + .map { arg -> arg.value as String } + .first() + + val cacheContractName = cacheContract.toClassName() + val methodName = "${cacheContractName.simpleName}Config" + val extractorType: ParameterizedTypeName + val returnType: KSClassDeclaration + val resolved = cacheType.resolve() + if (resolved.toClassName() == CAFFEINE_CACHE) { + returnType = resolver.getClassDeclarationByName(CAFFEINE_CACHE_CONFIG.canonicalName)!! + extractorType = CLASS_CONFIG_EXTRACTOR.parameterizedBy(returnType.asType(listOf()).toTypeName()) + } else if (resolved.toClassName() == REDIS_CACHE) { + returnType = resolver.getClassDeclarationByName(REDIS_CACHE_CONFIG.canonicalName)!! + extractorType = CLASS_CONFIG_EXTRACTOR.parameterizedBy(returnType.asType(listOf()).toTypeName()) + } else { + throw UnsupportedOperationException("Unknown implementation: $cacheContract") + } + + return FunSpec.builder(methodName) + .addAnnotation( + AnnotationSpec.builder(Tag::class) + .addMember(cacheContractName.simpleName + "::class") + .build() + ) + .addModifiers(KModifier.PUBLIC) + .addParameter("config", CLASS_CONFIG) + .addParameter("extractor", extractorType) + .addStatement("return extractor.extract(config.get(%S))!!", configPath) + .returns(returnType.asType(listOf()).toTypeName()) + .build() + } + + private fun getCacheImpl(cacheContract: KSClassDeclaration): ClassName { + val cacheImplName = cacheContract.toClassName() + return ClassName(cacheImplName.packageName, "$${cacheImplName.simpleName}Impl") + } + + private fun getCacheMethodImpl(cacheContract: KSClassDeclaration, cacheType: KSTypeReference): FunSpec { + val cacheImplName = getCacheImpl(cacheContract) + val methodName = "${cacheImplName.simpleName}Impl" + val resolved = cacheType.resolve() + return if (resolved.toClassName() == CAFFEINE_CACHE) { + FunSpec.builder(methodName) + .addModifiers(KModifier.PUBLIC) + .addParameter( + ParameterSpec.builder("config", CAFFEINE_CACHE_CONFIG) + .addAnnotation( + AnnotationSpec.builder(Tag::class) + .addMember("${cacheContract.simpleName.getShortName()}::class") + .build() + ) + .build() + ) + .addParameter("factory", CAFFEINE_CACHE_FACTORY) + .addParameter("telemetry", CAFFEINE_TELEMETRY) + .addStatement("return %T(config, factory, telemetry)", cacheImplName) + .returns(cacheContract.toTypeName()) + .build() + } else if (resolved.toClassName() == REDIS_CACHE) { + val keyType = cacheContract.typeParameters[0] + val valueType = cacheContract.typeParameters[1] + val keyMapperType = REDIS_CACHE_MAPPER_KEY.parameterizedBy(keyType.toTypeVariableName()) + val valueMapperType = REDIS_CACHE_MAPPER_VALUE.parameterizedBy(valueType.toTypeVariableName()) + FunSpec.builder(methodName) + .addModifiers(KModifier.PUBLIC) + .addParameter( + ParameterSpec.builder("config", REDIS_CACHE_CONFIG) + .addAnnotation( + AnnotationSpec.builder(Tag::class) + .addMember("${cacheContract.simpleName.getShortName()}::class") + .build() + ) + .build() + ) + .addParameter("syncClient", REDIS_CACHE_CLIENT_SYNC) + .addParameter("reactiveClient", REDIS_CACHE_CLIENT_REACTIVE) + .addParameter("telemetry", REDIS_TELEMETRY) + .addParameter("keyMapper", keyMapperType) + .addParameter("valueMapper", valueMapperType) + .addStatement("return new %L(config, syncClient, reactiveClient, telemetry, keyMapper, valueMapper)", methodName) + .returns(cacheContract.toTypeName()) + .build() + } else { + throw UnsupportedOperationException("Unknown implementation: $cacheContract") + } + } + + private fun getCacheConstructor(cacheContract: KSClassDeclaration, cacheType: KSTypeReference): FunSpec { + val resolved = cacheType.resolve() + return if (resolved.toClassName() == CAFFEINE_CACHE) { + FunSpec.constructorBuilder() + .addParameter("config", CAFFEINE_CACHE_CONFIG) + .addParameter("factory", CAFFEINE_CACHE_FACTORY) + .addParameter("telemetry", CAFFEINE_TELEMETRY) + .build() + } else if (resolved.toClassName() == REDIS_CACHE) { + val keyType = cacheContract.typeParameters[0] + val valueType = cacheContract.typeParameters[1] + val keyMapperType = REDIS_CACHE_MAPPER_KEY.parameterizedBy(keyType.toTypeVariableName()) + val valueMapperType = REDIS_CACHE_MAPPER_VALUE.parameterizedBy(valueType.toTypeVariableName()) + FunSpec.constructorBuilder() + .addParameter("config", REDIS_CACHE_CONFIG) + .addParameter("syncClient", REDIS_CACHE_CLIENT_SYNC) + .addParameter("reactiveClient", REDIS_CACHE_CLIENT_REACTIVE) + .addParameter("telemetry", REDIS_TELEMETRY) + .addParameter("keyMapper", keyMapperType) + .addParameter("valueMapper", valueMapperType) + .build() + } else { + throw UnsupportedOperationException("Unknown implementation: $cacheContract") + } + } + + private fun getCacheSuperConstructorCall(cacheContract: KSClassDeclaration, cacheType: KSTypeReference): CodeBlock { + val configPath = cacheContract.annotations + .filter { a -> a.annotationType.resolve().toClassName() == ANNOTATION_CACHE } + .flatMap { a -> a.arguments } + .filter { arg -> arg.name!!.getShortName() == "value" } + .map { arg -> arg.value as String } + .first() + + val resolved = cacheType.resolve() + return if (resolved.toClassName() == CAFFEINE_CACHE) { + CodeBlock.of("%S, config, factory, telemetry", configPath) + } else if (resolved.toClassName() == REDIS_CACHE) { + CodeBlock.of("%S, config, syncClient, reactiveClient, telemetry, keyMapper, valueMapper", configPath) + } else { + throw UnsupportedOperationException("Unknown implementation: $cacheContract") + } + } + + private fun getPackage(element: KSAnnotated): String { + return element.toString() + } +} + diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorProvider.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorProvider.kt similarity index 71% rename from cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorProvider.kt rename to cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorProvider.kt index 210817f34..8b8b8730d 100644 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorProvider.kt +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorProvider.kt @@ -6,12 +6,11 @@ import com.google.devtools.ksp.processing.SymbolProcessorEnvironment import com.google.devtools.ksp.processing.SymbolProcessorProvider @KspExperimental -class CacheKeySymbolProcessorProvider : SymbolProcessorProvider { +class CacheSymbolProcessorProvider : SymbolProcessorProvider { override fun create( environment: SymbolProcessorEnvironment ): SymbolProcessor { - CacheOperationManager.reset() - return CacheKeySymbolProcessor(environment, HashSet()) + return CacheSymbolProcessor(environment) } } diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/AbstractAopCacheAspect.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/AbstractAopCacheAspect.kt index 770d5494d..e6cd1fe4d 100644 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/AbstractAopCacheAspect.kt +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/AbstractAopCacheAspect.kt @@ -4,125 +4,38 @@ import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSFunctionDeclaration -import com.google.devtools.ksp.symbol.KSType -import com.google.devtools.ksp.symbol.Variance -import com.squareup.kotlinpoet.AnnotationSpec -import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.ClassName import ru.tinkoff.kora.aop.symbol.processor.KoraAspect -import ru.tinkoff.kora.cache.Cache -import ru.tinkoff.kora.cache.CacheManager -import ru.tinkoff.kora.cache.symbol.processor.CacheMeta import ru.tinkoff.kora.cache.symbol.processor.CacheOperation -import ru.tinkoff.kora.common.Tag -import ru.tinkoff.kora.ksp.common.exception.ProcessingError -import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException -import java.util.* -import java.util.stream.Collectors -import javax.tools.Diagnostic @KspExperimental abstract class AbstractAopCacheAspect : KoraAspect { - data class CacheMirrors(val manager: KSType, val cache: KSType) + private val KEY_CACHE = ClassName("ru.tinkoff.kora.cache", "CacheKey") - open fun getCacheMirrors(operation: CacheOperation, method: KSFunctionDeclaration, resolver: Resolver): CacheMirrors { - val keyElement = resolver.getClassDeclarationByName(resolver.getKSNameFromString(operation.key.canonicalName())) - ?: throw ProcessingErrorException( - ProcessingError( - "Cache Key is not yet generated, will try next round...", - method, - Diagnostic.Kind.WARNING, - ) - ) - - if (operation.value.canonicalName == null) { - throw ProcessingErrorException( - ProcessingError( - "Cache Return type is not yet known, will try next round...", - method, - Diagnostic.Kind.WARNING, - ) - ) - } - - val valueElement = resolver.getClassDeclarationByName(resolver.getKSNameFromString(operation.value.canonicalName)) - ?: throw ProcessingErrorException( - ProcessingError( - "Cache Return type is not yet known, will try next round...", - method, - Diagnostic.Kind.NOTE, - ) - ) - - val managerType = resolver.getClassDeclarationByName(CacheManager::class.java.canonicalName)?.asType( - listOf( - resolver.getTypeArgument(resolver.createKSTypeReferenceFromKSType(keyElement.asStarProjectedType()), Variance.INVARIANT), - resolver.getTypeArgument(resolver.createKSTypeReferenceFromKSType(valueElement.asStarProjectedType()), Variance.INVARIANT), - ) - ) ?: throw IllegalStateException("Can't extract declaration for: ${CacheManager::class.java.canonicalName}") - - val cacheType = resolver.getClassDeclarationByName(Cache::class.java.canonicalName)?.asType( - listOf( - resolver.getTypeArgument(resolver.createKSTypeReferenceFromKSType(keyElement.asStarProjectedType()), Variance.INVARIANT), - resolver.getTypeArgument(resolver.createKSTypeReferenceFromKSType(valueElement.asStarProjectedType()), Variance.INVARIANT), - ) - ) ?: throw IllegalStateException("Can't extract declaration for: ${Cache::class.java.canonicalName}") - - return CacheMirrors(managerType, cacheType) + open fun getCacheKey(operation: CacheOperation): ClassName { + return KEY_CACHE } open fun getCacheFields( operation: CacheOperation, - mirror: CacheMirrors, + resolver: Resolver, aspectContext: KoraAspect.AspectContext ): List { - val cacheFields = ArrayList() - for (manager in operation.meta.managers) { - val managerTags: List - if (manager.tags.isEmpty()) { - managerTags = listOf() - } else if (manager.tags.size == 1) { - managerTags = listOf( - AnnotationSpec.builder(Tag::class.java) - .addMember("value", manager.tags[0]) - .build() - ) - } else { - val tagValue: String = manager.tags.stream() - .collect(Collectors.joining(", ", "{", "}")) - - managerTags = listOf( - AnnotationSpec.builder(Tag::class.java) - .addMember("value", tagValue) - .build() - ) - } - - val fieldManager = aspectContext.fieldFactory.constructorParam(mirror.manager, managerTags) - val cacheField = aspectContext.fieldFactory.constructorInitialized(mirror.cache, - CodeBlock.of("%L.getCache(\"%L\")", fieldManager, manager.name)) - - cacheFields.add(cacheField) + val cacheFields: MutableList = ArrayList() + for (cacheImpl in operation.cacheImplementations) { + val cacheElement = resolver.getClassDeclarationByName(cacheImpl) + val fieldCache: String = aspectContext.fieldFactory.constructorParam(cacheElement!!.asType(listOf()), listOf()) + cacheFields.add(fieldCache) } - return cacheFields } open fun getKeyRecordParameters(operation: CacheOperation, method: KSFunctionDeclaration): String { - return operation.meta.getParametersNames(method).joinToString(", ") + return operation.getParametersNames(method).joinToString(", ") } open fun getSuperMethod(method: KSFunctionDeclaration, superCall: String): String { return method.parameters.joinToString(", ", "$superCall(", ")") } - - open fun getCacheParameters(operation: CacheOperation, fieldManagers: List): String { - val joiner = StringJoiner(", ") - for (i in fieldManagers.indices) { - val fieldManager = fieldManagers[i] - val manager: CacheMeta.Manager = operation.meta.managers[i] - joiner.add(fieldManager + ".getCache(\"" + manager.name + "\")") - } - return joiner.toString() - } } diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheInvalidateAopKoraAspect.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheInvalidateAopKoraAspect.kt index 991cb625a..2edb580f0 100644 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheInvalidateAopKoraAspect.kt +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheInvalidateAopKoraAspect.kt @@ -3,28 +3,43 @@ package ru.tinkoff.kora.cache.symbol.processor.aop import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono import ru.tinkoff.kora.aop.symbol.processor.KoraAspect -import ru.tinkoff.kora.cache.annotation.CacheInvalidate -import ru.tinkoff.kora.cache.annotation.CacheInvalidates -import ru.tinkoff.kora.cache.symbol.processor.CacheMeta import ru.tinkoff.kora.cache.symbol.processor.CacheOperation -import ru.tinkoff.kora.cache.symbol.processor.CacheOperationManager.Companion.getCacheOperation +import ru.tinkoff.kora.cache.symbol.processor.CacheOperationUtils.Companion.getCacheOperation +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlux +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFuture +import ru.tinkoff.kora.ksp.common.FunctionUtils.isMono import ru.tinkoff.kora.ksp.common.FunctionUtils.isVoid +import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException +import java.util.concurrent.Future @KspExperimental class CacheInvalidateAopKoraAspect(private val resolver: Resolver) : AbstractAopCacheAspect() { + private val ANNOTATION_CACHE_INVALIDATE = ClassName("ru.tinkoff.kora.cache.annotation", "CacheInvalidate") + private val ANNOTATION_CACHE_INVALIDATES = ClassName("ru.tinkoff.kora.cache.annotation", "CacheInvalidates") + override fun getSupportedAnnotationTypes(): Set { - return setOf(CacheInvalidate::class.java.canonicalName, CacheInvalidates::class.java.canonicalName) + return setOf(ANNOTATION_CACHE_INVALIDATE.canonicalName, ANNOTATION_CACHE_INVALIDATES.canonicalName) } override fun apply(method: KSFunctionDeclaration, superCall: String, aspectContext: KoraAspect.AspectContext): KoraAspect.ApplyResult { - val operation = getCacheOperation(method, resolver) - val cacheMirrors = getCacheMirrors(operation, method, resolver) - val cacheFields = getCacheFields(operation, cacheMirrors, aspectContext) + if (method.isFuture()) { + throw ProcessingErrorException("@CacheInvalidate can't be applied for types assignable from ${Future::class.java}", method) + } else if (method.isMono()) { + throw ProcessingErrorException("@CacheInvalidate can't be applied for types assignable from ${Mono::class.java}", method) + } else if (method.isFlux()) { + throw ProcessingErrorException("@CacheInvalidate can't be applied for types assignable from ${Flux::class.java}", method) + } + + val operation = getCacheOperation(method) + val cacheFields = getCacheFields(operation, resolver, aspectContext) - val body = if (operation.meta.type == CacheMeta.Type.EVICT_ALL) { + val body = if (operation.type == CacheOperation.Type.EVICT_ALL) { buildBodySyncAll(method, operation, superCall, cacheFields) } else { buildBodySync(method, operation, superCall, cacheFields) @@ -41,30 +56,42 @@ class CacheInvalidateAopKoraAspect(private val resolver: Resolver) : AbstractAop ): CodeBlock { val recordParameters = getKeyRecordParameters(operation, method) val superMethod = getSuperMethod(method, superCall) - val builder = StringBuilder() + val builder = CodeBlock.builder() + val isSingleNullableParam = operation.parameters.size == 1 && operation.parameters[0].type.resolve().isMarkedNullable // cache super method if (method.isVoid()) { - builder.append(superMethod).append("\n") + builder.add(superMethod).add("\n") } else { - builder.append("var value = ").append(superMethod).append("\n") + builder.add("var value = %L\n", superMethod) } // cache invalidate for (cache in cacheFields) { - builder.append(cache).append(".invalidate(_key)\n") + if (isSingleNullableParam) { + builder.add("_key?.let { %L.invalidate(it) }\n", cache) + } else { + builder.add("%L.invalidate(_key)\n", cache) + } } if (method.isVoid()) { - builder.append("return") + builder.add("return") } else { - builder.append("return value") + builder.add("return value") } - return CodeBlock.builder() - .add("var _key = %L(%L)\n", operation.key.simpleName, recordParameters) - .add(builder.toString()) - .build() + return if (operation.parameters.size == 1) { + CodeBlock.builder() + .add("val _key = %L\n", operation.parameters[0]) + .add(builder.build()) + .build() + } else { + CodeBlock.builder() + .add("val _key = %T.of(%L)\n", getCacheKey(operation), recordParameters) + .add(builder.build()) + .build() + } } private fun buildBodySyncAll( diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CachePutAopKoraAspect.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CachePutAopKoraAspect.kt index 9387fddde..f6076cbd1 100644 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CachePutAopKoraAspect.kt +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CachePutAopKoraAspect.kt @@ -3,25 +3,41 @@ package ru.tinkoff.kora.cache.symbol.processor.aop import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono import ru.tinkoff.kora.aop.symbol.processor.KoraAspect -import ru.tinkoff.kora.cache.annotation.CachePut -import ru.tinkoff.kora.cache.annotation.CachePuts import ru.tinkoff.kora.cache.symbol.processor.CacheOperation -import ru.tinkoff.kora.cache.symbol.processor.CacheOperationManager.Companion.getCacheOperation +import ru.tinkoff.kora.cache.symbol.processor.CacheOperationUtils.Companion.getCacheOperation +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlux +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFuture +import ru.tinkoff.kora.ksp.common.FunctionUtils.isMono import ru.tinkoff.kora.ksp.common.FunctionUtils.isSuspend +import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException +import java.util.concurrent.Future @KspExperimental class CachePutAopKoraAspect(private val resolver: Resolver) : AbstractAopCacheAspect() { + private val ANNOTATION_CACHE_PUT = ClassName("ru.tinkoff.kora.cache.annotation", "CachePut") + private val ANNOTATION_CACHE_PUTS = ClassName("ru.tinkoff.kora.cache.annotation", "CachePuts") + override fun getSupportedAnnotationTypes(): Set { - return setOf(CachePut::class.java.canonicalName, CachePuts::class.java.canonicalName) + return setOf(ANNOTATION_CACHE_PUT.canonicalName, ANNOTATION_CACHE_PUTS.canonicalName) } override fun apply(method: KSFunctionDeclaration, superCall: String, aspectContext: KoraAspect.AspectContext): KoraAspect.ApplyResult { - val operation = getCacheOperation(method, resolver) - val cacheMirrors = getCacheMirrors(operation, method, resolver) - val fieldManagers = getCacheFields(operation, cacheMirrors, aspectContext) + if (method.isFuture()) { + throw ProcessingErrorException("@CachePut can't be applied for types assignable from ${Future::class.java}", method) + } else if (method.isMono()) { + throw ProcessingErrorException("@CachePut can't be applied for types assignable from ${Mono::class.java}", method) + } else if (method.isFlux()) { + throw ProcessingErrorException("@CachePut can't be applied for types assignable from ${Flux::class.java}", method) + } + + val operation = getCacheOperation(method) + val fieldManagers = getCacheFields(operation, resolver, aspectContext) val body = if (method.isSuspend()) { buildBodySync(method, operation, superCall, fieldManagers) @@ -40,20 +56,27 @@ class CachePutAopKoraAspect(private val resolver: Resolver) : AbstractAopCacheAs ): CodeBlock { val recordParameters = getKeyRecordParameters(operation, method) val superMethod = getSuperMethod(method, superCall) - val builder = StringBuilder() + val builder = CodeBlock.builder() + val isSingleNullableParam = operation.parameters.size == 1 && operation.parameters[0].type.resolve().isMarkedNullable // cache super method - builder.append("var _value = ").append(superMethod).append("\n") + builder.add("val _value = ").add(superMethod).add("\n") + + if (operation.parameters.size == 1) { + builder.add("val _key = %L\n", operation.parameters[0]) + } else { + builder.add("val _key = %T.of(%L)\n", getCacheKey(operation), recordParameters) + } // cache put for (cache in cacheFields) { - builder.append(cache).append(".put(_key, _value)\n") + if (isSingleNullableParam) { + builder.add("_key?.let { %L.put(it, _value) }\n", cache) + } else { + builder.add("%L.put(_key, _value)\n", cache) + } } - builder.append("return _value") - return CodeBlock.builder() - .add("var _key = %L(%L)\n", operation.key.simpleName, recordParameters) - .add(builder.toString()) - .build() + return builder.add("return _value").build() } } diff --git a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheableAopKoraAspect.kt b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheableAopKoraAspect.kt index 588747816..d97f6efa4 100644 --- a/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheableAopKoraAspect.kt +++ b/cache/cache-symbol-processor/src/main/kotlin/ru/tinkoff/kora/cache/symbol/processor/aop/CacheableAopKoraAspect.kt @@ -1,32 +1,51 @@ package ru.tinkoff.kora.cache.symbol.processor.aop import com.google.devtools.ksp.KspExperimental +import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.ksp.toClassName +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono import ru.tinkoff.kora.aop.symbol.processor.KoraAspect -import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.annotation.Cacheables import ru.tinkoff.kora.cache.symbol.processor.CacheOperation -import ru.tinkoff.kora.cache.symbol.processor.CacheOperationManager.Companion.getCacheOperation +import ru.tinkoff.kora.cache.symbol.processor.CacheOperationUtils.Companion.getCacheOperation +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFlux +import ru.tinkoff.kora.ksp.common.FunctionUtils.isFuture +import ru.tinkoff.kora.ksp.common.FunctionUtils.isMono import ru.tinkoff.kora.ksp.common.FunctionUtils.isSuspend +import ru.tinkoff.kora.ksp.common.exception.ProcessingErrorException +import java.util.concurrent.Future @KspExperimental class CacheableAopKoraAspect(private val resolver: Resolver) : AbstractAopCacheAspect() { + private val CAFFEINE_CACHE = ClassName("ru.tinkoff.kora.cache.caffeine", "CaffeineCache") + private val ANNOTATION_CACHEABLE = ClassName("ru.tinkoff.kora.cache.annotation", "Cacheable") + private val ANNOTATION_CACHEABLES = ClassName("ru.tinkoff.kora.cache.annotation", "Cacheables") + override fun getSupportedAnnotationTypes(): Set { - return setOf(Cacheable::class.java.canonicalName, Cacheables::class.java.canonicalName) + return setOf(ANNOTATION_CACHEABLE.canonicalName, ANNOTATION_CACHEABLES.canonicalName) } override fun apply(method: KSFunctionDeclaration, superCall: String, aspectContext: KoraAspect.AspectContext): KoraAspect.ApplyResult { - val operation = getCacheOperation(method, resolver) - val cacheMirrors = getCacheMirrors(operation, method, resolver) + if (method.isFuture()) { + throw ProcessingErrorException("@Cacheable can't be applied for types assignable from ${Future::class.java}", method) + } else if (method.isMono()) { + throw ProcessingErrorException("@Cacheable can't be applied for types assignable from ${Mono::class.java}", method) + } else if (method.isFlux()) { + throw ProcessingErrorException("@Cacheable can't be applied for types assignable from ${Flux::class.java}", method) + } + + val operation = getCacheOperation(method) + val cacheFields = getCacheFields(operation, resolver, aspectContext) - val cacheFields = getCacheFields(operation, cacheMirrors, aspectContext) val body = if (method.isSuspend()) { - buildBodySync(method, operation, superCall, cacheFields) + buildBodySync(method, operation, superCall, cacheFields, resolver) } else { - buildBodySync(method, operation, superCall, cacheFields) + buildBodySync(method, operation, superCall, cacheFields, resolver) } return KoraAspect.ApplyResult.MethodBody(body) @@ -36,42 +55,85 @@ class CacheableAopKoraAspect(private val resolver: Resolver) : AbstractAopCacheA method: KSFunctionDeclaration, operation: CacheOperation, superCall: String, - cacheFields: List + cacheFields: List, + resolver: Resolver ): CodeBlock { val recordParameters = getKeyRecordParameters(operation, method) val superMethod = getSuperMethod(method, superCall) - val builder = StringBuilder() + val builder = CodeBlock.builder() + val isSingleNullableParam = operation.parameters.size == 1 && operation.parameters[0].type.resolve().isMarkedNullable + + val keyBlock = if (operation.parameters.size == 1) { + CodeBlock.of("val _key = %L\n", operation.parameters[0]) + } else { + CodeBlock.of("val _key = %T.of(%L)\n", getCacheKey(operation), recordParameters) + } + + if (!method.isSuspend() && operation.cacheImplementations.size == 1) { + val impl = resolver.getClassDeclarationByName(operation.cacheImplementations[0]) + if (impl != null) { + val codeBlock = if (isSingleNullableParam) { + CodeBlock.of( + """ + return if (_key != null) { + %L.computeIfAbsent(_key) { %L } + } else { + %L + } + """.trimIndent(), cacheFields[0], superMethod, superMethod + ) + } else { + CodeBlock.of("return %L.computeIfAbsent(_key) { %L }", cacheFields[0], superMethod) + } + + return CodeBlock.builder() + .add(keyBlock) + .add(codeBlock) + .build() + } + } // cache get for (i in cacheFields.indices) { val cache = cacheFields[i] val prefix = if (i == 0) "var _value = " else "_value = " - builder.append(prefix) - .append(cache).append(".get(_key)\n") - .append("if(_value != null) {\n"); + + if (isSingleNullableParam) { + builder.add(prefix) + .add("_key?.let { %L.get(it) }\n", cache) + .add("if(_value != null) {\n"); + } else { + builder.add(prefix) + .add("%L.get(_key)\n", cache) + .add("if(_value != null) {\n"); + } for (j in 0 until i) { val prevCache = cacheFields[j] - builder.append("\t").append(prevCache).append(".put(_key, _value)\n") + builder.add("\t%L.put(_key, _value)\n", prevCache) } builder - .append("\treturn _value\n") - .append("}\n\n") + .add("\treturn _value\n") + .add("}\n\n") } // cache super method - builder.append("_value = ").append(superMethod).append("\n") + builder.add("_value = %L\n", superMethod) // cache put for (cache in cacheFields) { - builder.append(cache).append(".put(_key, _value)\n") + if (isSingleNullableParam) { + builder.add("_key?.let { %L.put(it, _value) }\n", cache) + } else { + builder.add("%L.put(_key, _value)\n", cache) + } } - builder.append("return _value") + builder.add("return _value") return CodeBlock.builder() - .add("var _key = %L(%L)\n", operation.key.simpleName, recordParameters) - .add(builder.toString()) + .add(keyBlock) + .add(builder.build()) .build() } } diff --git a/cache/cache-symbol-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/cache/cache-symbol-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider index bb8d26359..285789666 100644 --- a/cache/cache-symbol-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider +++ b/cache/cache-symbol-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -1 +1 @@ -ru.tinkoff.kora.cache.symbol.processor.CacheKeySymbolProcessorProvider +ru.tinkoff.kora.cache.symbol.processor.CacheSymbolProcessorProvider diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorTests.kt deleted file mode 100644 index 59b250b5d..000000000 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheKeySymbolProcessorTests.kt +++ /dev/null @@ -1,140 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor - -import com.google.devtools.ksp.KspExperimental -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import ru.tinkoff.kora.cache.symbol.processor.testdata.* -import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.flux.CacheableTargetGetFlux -import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.flux.CacheableTargetPutFlux -import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.mono.CacheableTargetGetMono -import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.mono.CacheableTargetPutMono -import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.publisher.CacheableTargetGetPublisher -import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.publisher.CacheableTargetPutPublisher -import ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.CacheableTargetSuspend -import ru.tinkoff.kora.ksp.common.CompilationErrorException -import ru.tinkoff.kora.ksp.common.symbolProcess - -@KspExperimental -class CacheKeySymbolProcessorTests : Assertions() { - - @Test - @Throws(Exception::class) - fun cacheKeyRecordGeneratedForSync() { - val classLoader = symbolProcess( - CacheableTargetSync::class, - CacheKeySymbolProcessorProvider() - ) - val clazz = classLoader.loadClass("ru.tinkoff.kora.cache.symbol.processor.testdata._CacheKey__sync_cache") - assertNotNull(clazz) - } - - @Test - @Throws(Exception::class) - fun cacheKeyRecordGeneratedForSuspend() { - val classLoader = symbolProcess( - CacheableTargetSuspend::class, - CacheKeySymbolProcessorProvider() - ) - val clazz = classLoader.loadClass("ru.tinkoff.kora.cache.symbol.processor.testdata.suspended._CacheKey__suspend_cache") - assertNotNull(clazz) - } - - @Test - fun cacheKeyArgumentMissing() { - assertThrows( - CompilationErrorException::class.java - ) { - symbolProcess( - CacheableTargetArgumentMissing::class, - CacheKeySymbolProcessorProvider() - ) - } - } - - @Test - fun cacheKeyArgumentWrongOrder() { - assertThrows( - CompilationErrorException::class.java - ) { - symbolProcess( - CacheableTargetArgumentWrongOrder::class, - CacheKeySymbolProcessorProvider() - ) - } - } - - @Test - fun cacheKeyArgumentWrongType() { - assertThrows( - CompilationErrorException::class.java - ) { - symbolProcess( - CacheableTargetArgumentWrongType::class, - CacheKeySymbolProcessorProvider() - ) - } - } - - @Test - fun cacheNamePatternMismatch() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetNameInvalid::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cacheGetForVoidSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetGetVoid::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cachePutForVoidSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetPutVoid::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cacheGetForMonoSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetGetMono::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cachePutForMonoSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetPutMono::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cacheGetForFluxSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetGetFlux::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cachePutForFluxSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetPutFlux::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cacheGetForPublisherSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetGetPublisher::class, CacheKeySymbolProcessorProvider()) } - } - - @Test - fun cachePutForPublisherSignature() { - assertThrows( - CompilationErrorException::class.java - ) { symbolProcess(CacheableTargetPutPublisher::class, CacheKeySymbolProcessorProvider()) } - } -} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheRunner.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheRunner.kt new file mode 100644 index 000000000..3a8dfecac --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheRunner.kt @@ -0,0 +1,26 @@ +package ru.tinkoff.kora.cache.symbol.processor + +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import java.time.Duration + +class CacheRunner { + + companion object { + + fun getConfig() : CaffeineCacheConfig { + return object : CaffeineCacheConfig { + override fun expireAfterWrite(): Duration? { + return null; + } + + override fun expireAfterAccess(): Duration? { + return null; + } + + override fun initialSize(): Int? { + return null; + } + } + } + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorTests.kt new file mode 100644 index 000000000..4b01f91f1 --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/CacheSymbolProcessorTests.kt @@ -0,0 +1,103 @@ +package ru.tinkoff.kora.cache.symbol.processor + +import com.google.devtools.ksp.KspExperimental +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider +import ru.tinkoff.kora.cache.symbol.processor.testdata.* +import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.flux.CacheableGetFlux +import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.flux.CacheablePutFlux +import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.mono.CacheableGetMono +import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.mono.CacheablePutMono +import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.publisher.CacheableGetPublisher +import ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.publisher.CacheablePutPublisher +import ru.tinkoff.kora.ksp.common.CompilationErrorException +import ru.tinkoff.kora.ksp.common.symbolProcess + +@KspExperimental +class CacheSymbolProcessorTests { + + @Test + fun cacheKeyArgumentMissing() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableArgumentMissing::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheKeyArgumentWrongOrder() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableArgumentWrongOrder::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheKeyArgumentWrongType() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableArgumentWrongType::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheNamePatternMismatch() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableNameInvalid::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheGetForVoidSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableGetVoid::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cachePutForVoidSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheablePutVoid::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheGetForMonoSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableGetMono::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cachePutForMonoSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheablePutMono::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheGetForFluxSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableGetFlux::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cachePutForFluxSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheablePutFlux::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cacheGetForPublisherSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheableGetPublisher::class, AopSymbolProcessorProvider()) } + } + + @Test + fun cachePutForPublisherSignature() { + assertThrows( + CompilationErrorException::class.java + ) { symbolProcess(CacheablePutPublisher::class, AopSymbolProcessorProvider()) } + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheAopTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheAopTests.kt index a94947c78..115f8cb66 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheAopTests.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheAopTests.kt @@ -2,41 +2,50 @@ package ru.tinkoff.kora.cache.symbol.processor import com.google.devtools.ksp.KspExperimental import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager -import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableTargetSync -import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableTargetSyncMany -import ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.CacheableTargetSuspend +import ru.tinkoff.kora.cache.CacheKey +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 +import ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.CacheableSuspend import ru.tinkoff.kora.ksp.common.symbolProcess import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) @KspExperimental -class SuspendCacheAopTests : Assertions() { +class SuspendCacheAopTests : CaffeineCacheModule { - private val CACHED_SERVICE = "ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.\$CacheableTargetSuspend__AopProxy" + private val CACHE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache2Impl" + private val SERVICE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.\$CacheableSuspend__AopProxy" - private val cacheManager = DummyCacheManager() - private var cachedService: CacheableTargetSuspend? = null + private var cache: DummyCache2? = null + private var cachedService: CacheableSuspend? = null - private fun getService(manager: DummyCacheManager): CacheableTargetSuspend { - if(cachedService != null) { - return cachedService as CacheableTargetSuspend; + private fun getService(): CacheableSuspend { + if (cachedService != null) { + return cachedService as CacheableSuspend; } return try { val classLoader = symbolProcess( - CacheableTargetSuspend::class, - CacheKeySymbolProcessorProvider(), + listOf(DummyCache2::class, CacheableSuspend::class), + CacheSymbolProcessorProvider(), AopSymbolProcessorProvider(), ) - val serviceClass = classLoader.loadClass(CACHED_SERVICE) ?: throw IllegalArgumentException("Expected class not found: $CACHED_SERVICE") - val inst = serviceClass.constructors[0].newInstance(manager) as CacheableTargetSuspend - cachedService = inst + + val cacheClass = classLoader.loadClass(CACHE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE_CLASS") + cache = cacheClass.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache2 + + val serviceClass = classLoader.loadClass(SERVICE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $SERVICE_CLASS") + val inst = serviceClass.constructors[0].newInstance(cache) as CacheableSuspend inst } catch (e: Exception) { throw IllegalStateException(e.message, e) @@ -45,13 +54,13 @@ class SuspendCacheAopTests : Assertions() { @BeforeEach fun reset() { - cacheManager.reset() + cache?.invalidateAll() } @Test - fun getFromCacheWhenWasCacheEmpty() { + fun getWhenWasCacheEmpty() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -66,9 +75,9 @@ class SuspendCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheFilled() { + fun getWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -84,9 +93,9 @@ class SuspendCacheAopTests : Assertions() { } @Test - fun getFromCacheWrongKeyWhenCacheFilled() { + fun getWrongKeyWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -103,9 +112,9 @@ class SuspendCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheFilledOtherKey() { + fun getWhenCacheFilledOtherKey() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -122,9 +131,9 @@ class SuspendCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheInvalidate() { + fun getWhenCacheInvalidate() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -132,18 +141,25 @@ class SuspendCacheAopTests : Assertions() { val initial = runBlocking { service.getValue("1", BigDecimal.ZERO) } val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } assertEquals(initial, cached) + + val cached2 = runBlocking { service.putValue(BigDecimal.ZERO, "5", "2") } + assertEquals(initial, cached2) + service.value = "2" runBlocking { service.evictValue("1", BigDecimal.ZERO) } // then + assertNull(cache!!.get(CacheKey.of("1", BigDecimal.ZERO))) + assertEquals(cached2, cache!!.get(CacheKey.of("2", BigDecimal.ZERO))) + val fromCache = runBlocking { service.getValue("1", BigDecimal.ZERO) } assertNotEquals(cached, fromCache) } @Test - fun getFromCacheWhenCacheInvalidateAll() { + fun getWhenCacheInvalidateAll() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -151,10 +167,17 @@ class SuspendCacheAopTests : Assertions() { val initial = runBlocking { service.getValue("1", BigDecimal.ZERO) } val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } assertEquals(initial, cached) + + val cached2 = runBlocking { service.putValue(BigDecimal.ZERO, "5", "2") } + assertEquals(initial, cached2) + service.value = "2" runBlocking { service.evictAll() } // then + assertNull(cache!!.get(CacheKey.of("1", BigDecimal.ZERO))) + assertNull(cache!!.get(CacheKey.of("2", BigDecimal.ZERO))) + val fromCache = runBlocking { service.getValue("1", BigDecimal.ZERO) } assertNotEquals(cached, fromCache) } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheOneAopTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheOneAopTests.kt new file mode 100644 index 000000000..961855db6 --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendCacheOneAopTests.kt @@ -0,0 +1,169 @@ +package ru.tinkoff.kora.cache.symbol.processor + +import com.google.devtools.ksp.KspExperimental +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache1 +import ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.CacheableSuspendOne +import ru.tinkoff.kora.ksp.common.symbolProcess +import java.math.BigDecimal + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@KspExperimental +class SuspendCacheOneAopTests : CaffeineCacheModule { + + private val CACHE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache1Impl" + private val SERVICE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.\$CacheableSuspendOne__AopProxy" + + private var cache: DummyCache1? = null + private var cachedService: CacheableSuspendOne? = null + + private fun getService(): CacheableSuspendOne { + if (cachedService != null) { + return cachedService as CacheableSuspendOne + } + + return try { + val classLoader = symbolProcess( + listOf(DummyCache1::class, CacheableSuspendOne::class), + CacheSymbolProcessorProvider(), + AopSymbolProcessorProvider(), + ) + + val cacheClass = classLoader.loadClass(CACHE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE_CLASS") + cache = cacheClass.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache1 + + val serviceClass = classLoader.loadClass(SERVICE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $SERVICE_CLASS") + val inst = serviceClass.constructors[0].newInstance(cache) as CacheableSuspendOne + inst + } catch (e: Exception) { + throw IllegalStateException(e.message, e) + } + } + + @BeforeEach + fun reset() { + cache?.invalidateAll() + } + + @Test + fun getWhenWasCacheEmpty() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val notCached = runBlocking { service.getValue("1") } + service.value = "2" + + // then + val fromCache = runBlocking { service.getValue("1") } + assertEquals(notCached, fromCache) + assertNotEquals("2", fromCache) + } + + @Test + fun getWhenCacheFilled() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = runBlocking { service.getValue("1") } + val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } + assertEquals(initial, cached) + service.value = "2" + + // then + val fromCache = runBlocking { service.getValue("1") } + assertEquals(cached, fromCache) + } + + @Test + fun getWrongKeyWhenCacheFilled() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = runBlocking { service.getValue("1") } + val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } + assertEquals(initial, cached) + service.value = "2" + + // then + val fromCache = runBlocking { service.getValue("2") } + assertNotEquals(cached, fromCache) + assertEquals(service.value, fromCache) + } + + @Test + fun getWhenCacheFilledOtherKey() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } + service.value = "2" + val initial = runBlocking { service.getValue("2") } + assertNotEquals(cached, initial) + + // then + val fromCache = runBlocking { service.getValue("2") } + assertNotEquals(cached, fromCache) + assertEquals(initial, fromCache) + } + + @Test + fun getWhenCacheInvalidate() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = runBlocking { service.getValue("1") } + val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } + assertEquals(initial, cached) + service.value = "2" + runBlocking { service.evictValue("1") } + + // then + val fromCache = runBlocking { service.getValue("1") } + assertNotEquals(cached, fromCache) + } + + @Test + fun getWhenCacheInvalidateAll() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = runBlocking { service.getValue("1") } + val cached = runBlocking { service.putValue(BigDecimal.ZERO, "5", "1") } + assertEquals(initial, cached) + service.value = "2" + runBlocking { service.evictAll() } + + // then + val fromCache = runBlocking { service.getValue("1") } + assertNotEquals(cached, fromCache) + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendManyCacheAopTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendManyCacheAopTests.kt index 6ec802e8c..9e6f9fcf5 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendManyCacheAopTests.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SuspendManyCacheAopTests.kt @@ -2,40 +2,61 @@ package ru.tinkoff.kora.cache.symbol.processor import com.google.devtools.ksp.KspExperimental import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider import ru.tinkoff.kora.cache.CacheKey -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager -import ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.CacheableTargetSuspendMany +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache22 +import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableSyncMany +import ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.CacheableSuspendMany import ru.tinkoff.kora.ksp.common.symbolProcess import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) @KspExperimental -class SuspendManyCacheAopTests : Assertions() { +class SuspendManyCacheAopTests : CaffeineCacheModule { - private val CACHED_SERVICE = "ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.\$CacheableTargetSuspendMany__AopProxy" + private val CACHE1_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache2Impl" + private val CACHE2_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache22Impl" + private val SERVICE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testdata.suspended.\$CacheableSuspendMany__AopProxy" - private val cacheManager = DummyCacheManager() - private var cachedService: CacheableTargetSuspendMany? = null + private var cache1: DummyCache2? = null + private var cache2: DummyCache22? = null + private var cachedService: CacheableSyncMany? = null - private fun getService(manager: DummyCacheManager): CacheableTargetSuspendMany { - if(cachedService != null) { - return cachedService as CacheableTargetSuspendMany; + private fun getService(): CacheableSuspendMany { + if (cachedService != null) { + return cachedService as CacheableSuspendMany; } return try { val classLoader = symbolProcess( - CacheableTargetSuspendMany::class, - CacheKeySymbolProcessorProvider(), + listOf(DummyCache2::class, DummyCache22::class, CacheableSuspendMany::class), + CacheSymbolProcessorProvider(), AopSymbolProcessorProvider(), ) - val serviceClass = classLoader.loadClass(CACHED_SERVICE) ?: throw IllegalArgumentException("Expected class not found: $CACHED_SERVICE") - val inst = serviceClass.constructors[0].newInstance(manager) as CacheableTargetSuspendMany - cachedService = inst + + val cache1Class = classLoader.loadClass(CACHE1_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE1_CLASS") + cache1 = cache1Class.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache2 + + val cache2Class = classLoader.loadClass(CACHE2_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE2_CLASS") + cache2 = cache2Class.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache22 + + val serviceClass = classLoader.loadClass(SERVICE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $SERVICE_CLASS") + val inst = serviceClass.constructors[0].newInstance(cache1, cache2) as CacheableSuspendMany inst } catch (e: Exception) { throw IllegalStateException(e.message, e) @@ -44,13 +65,14 @@ class SuspendManyCacheAopTests : Assertions() { @BeforeEach fun reset() { - cacheManager.reset() + cache1?.invalidateAll() + cache2?.invalidateAll() } @Test - fun getFromCacheWhenWasCacheEmpty() { + fun getWhenWasCacheEmpty() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -65,27 +87,14 @@ class SuspendManyCacheAopTests : Assertions() { } @Test - fun getFromCacheLevel2AndThenSaveCacheLevel1() { + fun getLevel2AndThenSaveCacheLevel1() { // given - val service = getService(cacheManager) - val cache1 = cacheManager.getCache("suspend_cache") - val cache2 = cacheManager.getCache("suspend_cache_2") + val service = getService() service.value = "1" assertNotNull(service) - assertTrue(cache1.isEmpty()) - assertTrue(cache2.isEmpty()) + val cachedValue = "LEVEL_2" - val cachedKey: CacheKey = object : CacheKey { - override fun values(): List { - return listOf("1", BigDecimal.ZERO) - } - - override fun toString(): String { - return "1" + "-" + BigDecimal.ZERO - } - } - cache2.put(cachedKey, cachedValue) - assertFalse(cache2.isEmpty()) + cache2!!.put(CacheKey.of("1", BigDecimal.ZERO), cachedValue) // when val valueFromLevel2 = runBlocking { service.getValue("1", BigDecimal.ZERO) } @@ -95,14 +104,12 @@ class SuspendManyCacheAopTests : Assertions() { val valueFromLevel1 = runBlocking { service.getValue("1", BigDecimal.ZERO) } assertEquals(valueFromLevel2, valueFromLevel1) assertEquals(cachedValue, valueFromLevel1) - assertFalse(cache1.isEmpty()) - assertFalse(cache2.isEmpty()) } @Test - fun getFromCacheWhenCacheFilled() { + fun getWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -118,9 +125,9 @@ class SuspendManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWrongKeyWhenCacheFilled() { + fun getWrongKeyWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -137,9 +144,9 @@ class SuspendManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheFilledOtherKey() { + fun getWhenCacheFilledOtherKey() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -156,9 +163,9 @@ class SuspendManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheInvalidate() { + fun getWhenCacheInvalidate() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -175,9 +182,9 @@ class SuspendManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheInvalidateAll() { + fun getWhenCacheInvalidateAll() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheAopTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheAopTests.kt index 672905af8..e630c0954 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheAopTests.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheAopTests.kt @@ -1,40 +1,50 @@ package ru.tinkoff.kora.cache.symbol.processor import com.google.devtools.ksp.KspExperimental -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager -import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableTargetSync -import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableTargetSyncMany +import ru.tinkoff.kora.cache.CacheKey +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 +import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableSync import ru.tinkoff.kora.ksp.common.symbolProcess import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) @KspExperimental -class SyncCacheAopTests : Assertions() { +class SyncCacheAopTests : CaffeineCacheModule { - private val CACHED_SERVICE = "ru.tinkoff.kora.cache.symbol.processor.testdata.\$CacheableTargetSync__AopProxy" + private val CACHE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache2Impl" + private val SERVICE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testdata.\$CacheableSync__AopProxy" - private val cacheManager = DummyCacheManager() - private var cachedService: CacheableTargetSync? = null + private var cache: DummyCache2? = null + private var cachedService: CacheableSync? = null - private fun getService(manager: DummyCacheManager): CacheableTargetSync { - if(cachedService != null) { - return cachedService as CacheableTargetSync; + private fun getService(): CacheableSync { + if (cachedService != null) { + return cachedService as CacheableSync; } return try { val classLoader = symbolProcess( - CacheableTargetSync::class, - CacheKeySymbolProcessorProvider(), + listOf(DummyCache2::class, CacheableSync::class), + CacheSymbolProcessorProvider(), AopSymbolProcessorProvider(), ) - val serviceClass = classLoader.loadClass(CACHED_SERVICE) ?: throw IllegalArgumentException("Expected class not found: $CACHED_SERVICE") - val inst = serviceClass.constructors[0].newInstance(manager) as CacheableTargetSync - cachedService = inst + + val cacheClass = classLoader.loadClass(CACHE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE_CLASS") + cache = cacheClass.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache2 + + val serviceClass = classLoader.loadClass(SERVICE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $SERVICE_CLASS") + val inst = serviceClass.constructors[0].newInstance(cache) as CacheableSync inst } catch (e: Exception) { throw IllegalStateException(e.message, e) @@ -43,13 +53,13 @@ class SyncCacheAopTests : Assertions() { @BeforeEach fun reset() { - cacheManager.reset() + cache?.invalidateAll() } @Test - fun getFromCacheWhenWasCacheEmpty() { + fun getWhenWasCacheEmpty() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -64,9 +74,9 @@ class SyncCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheFilled() { + fun getWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -82,9 +92,9 @@ class SyncCacheAopTests : Assertions() { } @Test - fun getFromCacheWrongKeyWhenCacheFilled() { + fun getWrongKeyWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -101,9 +111,9 @@ class SyncCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheFilledOtherKey() { + fun getWhenCacheFilledOtherKey() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -120,9 +130,9 @@ class SyncCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheInvalidate() { + fun getWhenCacheInvalidate() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -130,18 +140,25 @@ class SyncCacheAopTests : Assertions() { val initial = service.getValue("1", BigDecimal.ZERO) val cached = service.putValue(BigDecimal.ZERO, "5", "1") assertEquals(initial, cached) + + val cached2 = service.putValue(BigDecimal.ZERO, "5", "2") + assertEquals(initial, cached2) + service.value = "2" service.evictValue("1", BigDecimal.ZERO) // then + assertNull(cache!!.get(CacheKey.of("1", BigDecimal.ZERO))) + assertEquals(cached2, cache!!.get(CacheKey.of("2", BigDecimal.ZERO))) + val fromCache = service.getValue("1", BigDecimal.ZERO) assertNotEquals(cached, fromCache) } @Test - fun getFromCacheWhenCacheInvalidateAll() { + fun getWhenCacheInvalidateAll() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -149,10 +166,17 @@ class SyncCacheAopTests : Assertions() { val initial = service.getValue("1", BigDecimal.ZERO) val cached = service.putValue(BigDecimal.ZERO, "5", "1") assertEquals(initial, cached) + + val cached2 = service.putValue(BigDecimal.ZERO, "5", "2") + assertEquals(initial, cached2) + service.value = "2" service.evictAll() // then + assertNull(cache!!.get(CacheKey.of("1", BigDecimal.ZERO))) + assertNull(cache!!.get(CacheKey.of("2", BigDecimal.ZERO))) + val fromCache = service.getValue("1", BigDecimal.ZERO) assertNotEquals(cached, fromCache) } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheOneAopTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheOneAopTests.kt new file mode 100644 index 000000000..ecb4bf71c --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncCacheOneAopTests.kt @@ -0,0 +1,168 @@ +package ru.tinkoff.kora.cache.symbol.processor + +import com.google.devtools.ksp.KspExperimental +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache1 +import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableSyncOne +import ru.tinkoff.kora.ksp.common.symbolProcess +import java.math.BigDecimal + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@KspExperimental +class SyncCacheOneAopTests : CaffeineCacheModule { + + private val CACHE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache1Impl" + private val SERVICE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testdata.\$CacheableSyncOne__AopProxy" + + private var cache: DummyCache1? = null + private var cachedService: CacheableSyncOne? = null + + private fun getService(): CacheableSyncOne { + if (cachedService != null) { + return cachedService as CacheableSyncOne; + } + + return try { + val classLoader = symbolProcess( + listOf(DummyCache1::class, CacheableSyncOne::class), + CacheSymbolProcessorProvider(), + AopSymbolProcessorProvider(), + ) + + val cacheClass = classLoader.loadClass(CACHE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE_CLASS") + cache = cacheClass.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache1 + + val serviceClass = classLoader.loadClass(SERVICE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $SERVICE_CLASS") + val inst = serviceClass.constructors[0].newInstance(cache) as CacheableSyncOne + inst + } catch (e: Exception) { + throw IllegalStateException(e.message, e) + } + } + + @BeforeEach + fun reset() { + cache?.invalidateAll() + } + + @Test + fun getWhenWasCacheEmpty() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val notCached = service.getValue("1") + service.value = "2" + + // then + val fromCache = service.getValue("1") + assertEquals(notCached, fromCache) + assertNotEquals("2", fromCache) + } + + @Test + fun getWhenCacheFilled() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = service.getValue("1") + val cached = service.putValue(BigDecimal.ZERO, "5", "1") + assertEquals(initial, cached) + service.value = "2" + + // then + val fromCache = service.getValue("1") + assertEquals(cached, fromCache) + } + + @Test + fun getWrongKeyWhenCacheFilled() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = service.getValue("1") + val cached = service.putValue(BigDecimal.ZERO, "5", "1") + assertEquals(initial, cached) + service.value = "2" + + // then + val fromCache = service.getValue("2") + assertNotEquals(cached, fromCache) + assertEquals(service.value, fromCache) + } + + @Test + fun getWhenCacheFilledOtherKey() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val cached = service.putValue(BigDecimal.ZERO, "5", "1") + service.value = "2" + val initial = service.getValue("2") + assertNotEquals(cached, initial) + + // then + val fromCache = service.getValue("2") + assertNotEquals(cached, fromCache) + assertEquals(initial, fromCache) + } + + @Test + fun getWhenCacheInvalidate() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = service.getValue("1") + val cached = service.putValue(BigDecimal.ZERO, "5", "1") + assertEquals(initial, cached) + service.value = "2" + service.evictValue("1") + + // then + val fromCache = service.getValue("1") + assertNotEquals(cached, fromCache) + } + + @Test + fun getWhenCacheInvalidateAll() { + // given + val service = getService() + service.value = "1" + assertNotNull(service) + + // when + val initial = service.getValue("1") + val cached = service.putValue(BigDecimal.ZERO, "5", "1") + assertEquals(initial, cached) + service.value = "2" + service.evictAll() + + // then + val fromCache = service.getValue("1") + assertNotEquals(cached, fromCache) + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncManyCacheAopTests.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncManyCacheAopTests.kt index 961b2d7dd..b6f1a3cfe 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncManyCacheAopTests.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/SyncManyCacheAopTests.kt @@ -1,41 +1,62 @@ package ru.tinkoff.kora.cache.symbol.processor import com.google.devtools.ksp.KspExperimental -import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import ru.tinkoff.kora.aop.symbol.processor.AopSymbolProcessorProvider import ru.tinkoff.kora.cache.CacheKey -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager -import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableTargetSyncMany +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheConfig +import ru.tinkoff.kora.cache.caffeine.CaffeineCacheModule +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache22 +import ru.tinkoff.kora.cache.symbol.processor.testdata.CacheableSyncMany import ru.tinkoff.kora.ksp.common.symbolProcess import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) @KspExperimental -class SyncManyCacheAopTests : Assertions() { +class SyncManyCacheAopTests : CaffeineCacheModule { - private val CACHED_SERVICE = "ru.tinkoff.kora.cache.symbol.processor.testdata.\$CacheableTargetSyncMany__AopProxy" + private val CACHE1_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache2Impl" + private val CACHE2_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testcache.\$DummyCache22Impl" + private val SERVICE_CLASS = "ru.tinkoff.kora.cache.symbol.processor.testdata.\$CacheableSyncMany__AopProxy" - private val cacheManager = DummyCacheManager() - private var cachedService: CacheableTargetSyncMany? = null + private var cache1: DummyCache2? = null + private var cache2: DummyCache22? = null + private var cachedService: CacheableSyncMany? = null - private fun getService(manager: DummyCacheManager): CacheableTargetSyncMany { - if(cachedService != null) { - return cachedService as CacheableTargetSyncMany; + private fun getService(): CacheableSyncMany { + if (cachedService != null) { + return cachedService as CacheableSyncMany; } return try { val classLoader = symbolProcess( - CacheableTargetSyncMany::class, + listOf(DummyCache2::class, DummyCache22::class, CacheableSyncMany::class), AopSymbolProcessorProvider(), - CacheKeySymbolProcessorProvider() + CacheSymbolProcessorProvider() ) - val serviceClass = classLoader.loadClass(CACHED_SERVICE) ?: throw IllegalArgumentException("Expected class not found: $CACHED_SERVICE") - val instance = serviceClass.constructors[0].newInstance(manager) as CacheableTargetSyncMany - cachedService = instance; - instance + + + val cache1Class = classLoader.loadClass(CACHE1_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE1_CLASS") + cache1 = cache1Class.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache2 + + val cache2Class = classLoader.loadClass(CACHE2_CLASS) ?: throw IllegalArgumentException("Expected class not found: $CACHE2_CLASS") + cache2 = cache2Class.constructors[0].newInstance( + CacheRunner.getConfig(), + caffeineCacheFactory(null), + caffeineCacheTelemetry(null, null) + ) as DummyCache22 + + val serviceClass = classLoader.loadClass(SERVICE_CLASS) ?: throw IllegalArgumentException("Expected class not found: $SERVICE_CLASS") + val inst = serviceClass.constructors[0].newInstance(cache1, cache2) as CacheableSyncMany + inst } catch (e: Exception) { throw IllegalStateException(e.message, e) } @@ -43,13 +64,14 @@ class SyncManyCacheAopTests : Assertions() { @BeforeEach fun reset() { - cacheManager.reset() + cache1?.invalidateAll() + cache2?.invalidateAll() } @Test - fun getFromCacheWhenWasCacheEmpty() { + fun getWhenWasCacheEmpty() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -64,27 +86,14 @@ class SyncManyCacheAopTests : Assertions() { } @Test - fun getFromCacheLevel2AndThenSaveCacheLevel1() { + fun getLevel2AndThenSaveCacheLevel1() { // given - val service = getService(cacheManager) - val cache1 = cacheManager.getCache("sync_cache") - val cache2 = cacheManager.getCache("sync_cache_2") + val service = getService() service.value = "1" assertNotNull(service) - assertTrue(cache1.isEmpty()) - assertTrue(cache2.isEmpty()) + val cachedValue = "LEVEL_2" - val cachedKey: CacheKey = object : CacheKey { - override fun values(): List { - return listOf("1", BigDecimal.ZERO) - } - - override fun toString(): String { - return "1" + "-" + BigDecimal.ZERO - } - } - cache2.put(cachedKey, cachedValue) - assertFalse(cache2.isEmpty()) + cache2!!.put(CacheKey.of("1", BigDecimal.ZERO), cachedValue) // when val valueFromLevel2 = service.getValue("1", BigDecimal.ZERO) @@ -94,14 +103,12 @@ class SyncManyCacheAopTests : Assertions() { val valueFromLevel1 = service.getValue("1", BigDecimal.ZERO) assertEquals(valueFromLevel2, valueFromLevel1) assertEquals(cachedValue, valueFromLevel1) - assertFalse(cache1.isEmpty()) - assertFalse(cache2.isEmpty()) } @Test - fun getFromCacheWhenCacheFilled() { + fun getWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -117,9 +124,9 @@ class SyncManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWrongKeyWhenCacheFilled() { + fun getWrongKeyWhenCacheFilled() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -136,9 +143,9 @@ class SyncManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheFilledOtherKey() { + fun getWhenCacheFilledOtherKey() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -155,9 +162,9 @@ class SyncManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheInvalidate() { + fun getWhenCacheInvalidate() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) @@ -174,9 +181,9 @@ class SyncManyCacheAopTests : Assertions() { } @Test - fun getFromCacheWhenCacheInvalidateAll() { + fun getWhenCacheInvalidateAll() { // given - val service = getService(cacheManager) + val service = getService() service.value = "1" assertNotNull(service) diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache.kt deleted file mode 100644 index f2ab4e91d..000000000 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache.kt +++ /dev/null @@ -1,62 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor.testcache - -import reactor.core.publisher.Mono -import ru.tinkoff.kora.cache.Cache -import ru.tinkoff.kora.cache.LoadableCache - -class DummyCache(private val name: String) : Cache, LoadableCache { - - private val cache: MutableMap = HashMap() - - fun origin(): String { - return "dummy"; - } - - fun name(): String { - return name - } - - override operator fun get(key: K): V? { - return cache[key.toString()] - } - - override fun put(key: K, value: V): V { - cache[key.toString()] = value - return value - } - - override fun invalidate(key: K) { - cache.remove(key.toString()) - } - - override fun invalidateAll() { - cache.clear() - } - - override fun getAsync(key: K): Mono { - return Mono.justOrEmpty(get(key)) - } - - override fun putAsync(key: K, value: V): Mono { - put(key, value) - return Mono.just(value) - } - - override fun invalidateAsync(key: K): Mono { - invalidate(key) - return Mono.empty() - } - - override fun invalidateAllAsync(): Mono { - invalidateAll() - return Mono.empty() - } - - fun isEmpty(): Boolean { - return cache.isEmpty() - } - - fun reset() { - cache.clear() - } -} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache1.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache1.kt new file mode 100644 index 000000000..a8ae071ff --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache1.kt @@ -0,0 +1,8 @@ +package ru.tinkoff.kora.cache.symbol.processor.testcache + +import ru.tinkoff.kora.cache.annotation.Cache +import ru.tinkoff.kora.cache.caffeine.CaffeineCache + +@Cache("dummy1") +interface DummyCache1 : CaffeineCache + diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache2.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache2.kt new file mode 100644 index 000000000..6ad059fde --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache2.kt @@ -0,0 +1,9 @@ +package ru.tinkoff.kora.cache.symbol.processor.testcache + +import ru.tinkoff.kora.cache.CacheKey +import ru.tinkoff.kora.cache.annotation.Cache +import ru.tinkoff.kora.cache.caffeine.CaffeineCache +import java.math.BigDecimal + +@Cache("dummy2") +interface DummyCache2 : CaffeineCache, String> diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache22.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache22.kt new file mode 100644 index 000000000..753b1df5e --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCache22.kt @@ -0,0 +1,9 @@ +package ru.tinkoff.kora.cache.symbol.processor.testcache + +import ru.tinkoff.kora.cache.CacheKey +import ru.tinkoff.kora.cache.annotation.Cache +import ru.tinkoff.kora.cache.caffeine.CaffeineCache +import java.math.BigDecimal + +@Cache("dummy22") +interface DummyCache22 : CaffeineCache, String> diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCacheManager.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCacheManager.kt deleted file mode 100644 index 58dce1a7c..000000000 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testcache/DummyCacheManager.kt +++ /dev/null @@ -1,18 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor.testcache - -import ru.tinkoff.kora.cache.Cache -import ru.tinkoff.kora.cache.CacheManager -import ru.tinkoff.kora.cache.LoadableCache - -class DummyCacheManager : CacheManager { - - private val cacheMap = HashMap>() - - override fun getCache(name: String): DummyCache { - return cacheMap.computeIfAbsent(name) { DummyCache(name) } - } - - fun reset() { - cacheMap.values.forEach { c -> c.reset() } - } -} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentMissing.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentMissing.kt similarity index 67% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentMissing.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentMissing.kt index 6eeae35bb..7e974aa4a 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentMissing.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentMissing.kt @@ -2,18 +2,18 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetArgumentMissing { +class CacheableArgumentMissing { var value = "1" - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg4"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg4"]) fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentWrongType.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentWrongOrder.kt similarity index 67% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentWrongType.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentWrongOrder.kt index 62a24843b..b9e8b1e6b 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentWrongType.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentWrongOrder.kt @@ -2,18 +2,18 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetArgumentWrongType { +class CacheableArgumentWrongOrder { var value = "1" - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg3"]) + @CachePut(value = DummyCache2::class, parameters = ["arg2", "arg1"]) fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentWrongOrder.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentWrongType.kt similarity index 67% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentWrongOrder.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentWrongType.kt index 4abb47126..ad8bfe56f 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetArgumentWrongOrder.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableArgumentWrongType.kt @@ -2,18 +2,18 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetArgumentWrongOrder { +class CacheableArgumentWrongType { var value = "1" - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class], parameters = ["arg2", "arg1"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg3"]) fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetGetVoid.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableGetVoid.kt similarity index 69% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetGetVoid.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableGetVoid.kt index 855c6c6a9..849f1cbdd 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetGetVoid.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableGetVoid.kt @@ -2,17 +2,17 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetGetVoid { +class CacheableGetVoid { var value = "1" - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?) { } - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class]) + @CachePut(value = DummyCache2::class) fun putValue(arg1: String?, arg2: BigDecimal?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetNameInvalid.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableNameInvalid.kt similarity index 73% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetNameInvalid.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableNameInvalid.kt index b4cfcc0ab..30e92f047 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetNameInvalid.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableNameInvalid.kt @@ -1,13 +1,13 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CachePut -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetNameInvalid { +class CacheableNameInvalid { var value = "1" - @CachePut(name = "my-cache", tags = [DummyCacheManager::class]) + @CachePut(value = DummyCache2::class) fun putValue(arg1: String?, arg2: BigDecimal?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetPutVoid.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheablePutVoid.kt similarity index 66% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetPutVoid.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheablePutVoid.kt index c765a47e7..3ad4291f7 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetPutVoid.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheablePutVoid.kt @@ -2,18 +2,18 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetPutVoid { +class CacheablePutVoid { var value = "1" - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) fun putValue(arg1: String?, arg2: BigDecimal?) { } } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSync.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSync.kt similarity index 61% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSync.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSync.kt index 32baeec32..65bbe1f80 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSync.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSync.kt @@ -3,27 +3,27 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata import ru.tinkoff.kora.cache.annotation.CacheInvalidate import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -open class CacheableTargetSync { +open class CacheableSync { var value = "1" - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) open fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) open fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { return value } - @CacheInvalidate(name = "sync_cache", tags = [DummyCacheManager::class]) + @CacheInvalidate(value = DummyCache2::class) open fun evictValue(arg1: String?, arg2: BigDecimal?) { } - @CacheInvalidate(name = "sync_cache", tags = [DummyCacheManager::class], invalidateAll = true) + @CacheInvalidate(value = DummyCache2::class, invalidateAll = true) open fun evictAll() { } } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncMany.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncMany.kt new file mode 100644 index 000000000..390ffe39c --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncMany.kt @@ -0,0 +1,34 @@ +package ru.tinkoff.kora.cache.symbol.processor.testdata + +import ru.tinkoff.kora.cache.annotation.CacheInvalidate +import ru.tinkoff.kora.cache.annotation.CachePut +import ru.tinkoff.kora.cache.annotation.Cacheable +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache22 +import java.math.BigDecimal + +open class CacheableSyncMany { + var value = "1" + + @Cacheable(value = DummyCache2::class) + @Cacheable(value = DummyCache22::class) + open fun getValue(arg1: String?, arg2: BigDecimal?): String { + return value + } + + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache22::class, parameters = ["arg1", "arg2"]) + open fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { + return value + } + + @CacheInvalidate(value = DummyCache2::class) + @CacheInvalidate(value = DummyCache22::class) + open fun evictValue(arg1: String?, arg2: BigDecimal?) { + } + + @CacheInvalidate(value = DummyCache2::class, invalidateAll = true) + @CacheInvalidate(value = DummyCache22::class, invalidateAll = true) + open fun evictAll() { + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncOne.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncOne.kt new file mode 100644 index 000000000..7084bad6a --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableSyncOne.kt @@ -0,0 +1,29 @@ +package ru.tinkoff.kora.cache.symbol.processor.testdata + +import ru.tinkoff.kora.cache.annotation.CacheInvalidate +import ru.tinkoff.kora.cache.annotation.CachePut +import ru.tinkoff.kora.cache.annotation.Cacheable +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache1 +import java.math.BigDecimal + +open class CacheableSyncOne { + var value = "1" + + @Cacheable(value = DummyCache1::class) + open fun getValue(arg1: String?): String { + return value + } + + @CachePut(value = DummyCache1::class, parameters = ["arg1"]) + open fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { + return value + } + + @CacheInvalidate(value = DummyCache1::class) + open fun evictValue(arg1: String?) { + } + + @CacheInvalidate(value = DummyCache1::class, invalidateAll = true) + open fun evictAll() { + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSyncMany.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSyncMany.kt deleted file mode 100644 index 459ac2ce8..000000000 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/CacheableTargetSyncMany.kt +++ /dev/null @@ -1,33 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor.testdata - -import ru.tinkoff.kora.cache.annotation.CacheInvalidate -import ru.tinkoff.kora.cache.annotation.CachePut -import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager -import java.math.BigDecimal - -open class CacheableTargetSyncMany { - var value = "1" - - @Cacheable(name = "sync_cache", tags = [DummyCacheManager::class]) - @Cacheable(name = "sync_cache_2", tags = [DummyCacheManager::class]) - open fun getValue(arg1: String?, arg2: BigDecimal?): String { - return value - } - - @CachePut(name = "sync_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) - @CachePut(name = "sync_cache_2", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) - open fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { - return value - } - - @CacheInvalidate(name = "sync_cache", tags = [DummyCacheManager::class]) - @CacheInvalidate(name = "sync_cache_2", tags = [DummyCacheManager::class]) - open fun evictValue(arg1: String?, arg2: BigDecimal?) { - } - - @CacheInvalidate(name = "sync_cache", tags = [DummyCacheManager::class], invalidateAll = true) - @CacheInvalidate(name = "sync_cache_2", tags = [DummyCacheManager::class], invalidateAll = true) - open fun evictAll() { - } -} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableTargetGetFlux.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableGetFlux.kt similarity index 73% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableTargetGetFlux.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableGetFlux.kt index a8d5b52ab..aa3b7ad36 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableTargetGetFlux.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableGetFlux.kt @@ -3,18 +3,18 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.flux import reactor.core.publisher.Flux import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetGetFlux { +class CacheableGetFlux { var value = "1" - @Cacheable(name = "flux_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): Flux { return Flux.just(value) } - @CachePut(name = "flux_cache", tags = [DummyCacheManager::class]) + @CachePut(value = DummyCache2::class) fun putValue(arg1: String?, arg2: BigDecimal?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableTargetPutFlux.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheablePutFlux.kt similarity index 70% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableTargetPutFlux.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheablePutFlux.kt index b3120732d..4df63e5a7 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheableTargetPutFlux.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/flux/CacheablePutFlux.kt @@ -3,18 +3,18 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.flux import reactor.core.publisher.Flux import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetPutFlux { +class CacheablePutFlux { var value = "1" - @Cacheable(name = "flux_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "flux_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) fun putValue(arg1: String?, arg2: BigDecimal?): Flux { return Flux.just(value) } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableTargetGetMono.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableGetMono.kt similarity index 70% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableTargetGetMono.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableGetMono.kt index 2a66f6dd0..0544ce6a0 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableTargetGetMono.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableGetMono.kt @@ -1,21 +1,20 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.mono -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetGetMono { +class CacheableGetMono { var value = "1" - @Cacheable(name = "flux_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): Mono { return Mono.just(value) } - @CachePut(name = "flux_cache", tags = [DummyCacheManager::class]) + @CachePut(value = DummyCache2::class) fun putValue(arg1: String?, arg2: BigDecimal?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableTargetPutMono.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheablePutMono.kt similarity index 67% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableTargetPutMono.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheablePutMono.kt index 69a1c40ac..da80eb71c 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheableTargetPutMono.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/mono/CacheablePutMono.kt @@ -1,21 +1,20 @@ package ru.tinkoff.kora.cache.symbol.processor.testdata.reactive.mono -import reactor.core.publisher.Flux import reactor.core.publisher.Mono import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetPutMono { +class CacheablePutMono { var value = "1" - @Cacheable(name = "flux_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "flux_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) fun putValue(arg1: String?, arg2: BigDecimal?): Mono { return Mono.just(value) } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableTargetGetPublisher.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableGetPublisher.kt similarity index 74% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableTargetGetPublisher.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableGetPublisher.kt index 56a6c0666..4e7470845 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableTargetGetPublisher.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableGetPublisher.kt @@ -4,18 +4,18 @@ import org.reactivestreams.Publisher import reactor.core.publisher.Flux import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetGetPublisher { +class CacheableGetPublisher { var value = "1" - @Cacheable(name = "publisher_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): Publisher { return Flux.just(value) } - @CachePut(name = "publisher_cache", tags = [DummyCacheManager::class]) + @CachePut(value = DummyCache2::class) fun putValue(arg1: String?, arg2: BigDecimal?): String { return value } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableTargetPutPublisher.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheablePutPublisher.kt similarity index 71% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableTargetPutPublisher.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheablePutPublisher.kt index 17c636ff3..26b3005c1 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheableTargetPutPublisher.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/reactive/publisher/CacheablePutPublisher.kt @@ -4,19 +4,19 @@ import org.reactivestreams.Publisher import reactor.core.publisher.Flux import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -class CacheableTargetPutPublisher { +class CacheablePutPublisher { var value = "1" - @Cacheable(name = "publisher_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) fun getValue(arg1: String?, arg2: BigDecimal?): String { return value } - @CachePut(name = "publisher_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) fun putValue(arg1: String?, arg2: BigDecimal?): Publisher { return Flux.just(value) } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspend.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspend.kt similarity index 65% rename from cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspend.kt rename to cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspend.kt index 0cb660166..13f7ee16e 100644 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspend.kt +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspend.kt @@ -4,30 +4,30 @@ import kotlinx.coroutines.delay import ru.tinkoff.kora.cache.annotation.CacheInvalidate import ru.tinkoff.kora.cache.annotation.CachePut import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 import java.math.BigDecimal -open class CacheableTargetSuspend { +open class CacheableSuspend { var value = "1" - @Cacheable(name = "suspend_cache", tags = [DummyCacheManager::class]) + @Cacheable(value = DummyCache2::class) open suspend fun getValue(arg1: String?, arg2: BigDecimal?): String { delay(10) return value } - @CachePut(name = "suspend_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) open suspend fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { delay(10) return value } - @CacheInvalidate(name = "suspend_cache", tags = [DummyCacheManager::class]) + @CacheInvalidate(value = DummyCache2::class) open suspend fun evictValue(arg1: String?, arg2: BigDecimal?) { delay(10) } - @CacheInvalidate(name = "suspend_cache", tags = [DummyCacheManager::class], invalidateAll = true) + @CacheInvalidate(value = DummyCache2::class, invalidateAll = true) open suspend fun evictAll() { delay(10) } diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendMany.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendMany.kt new file mode 100644 index 000000000..f839391ca --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendMany.kt @@ -0,0 +1,39 @@ +package ru.tinkoff.kora.cache.symbol.processor.testdata.suspended + +import kotlinx.coroutines.delay +import ru.tinkoff.kora.cache.annotation.CacheInvalidate +import ru.tinkoff.kora.cache.annotation.CachePut +import ru.tinkoff.kora.cache.annotation.Cacheable +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache2 +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache22 +import java.math.BigDecimal + +open class CacheableSuspendMany { + var value = "1" + + @Cacheable(value = DummyCache2::class) + @Cacheable(value = DummyCache22::class) + open suspend fun getValue(arg1: String?, arg2: BigDecimal?): String { + delay(10) + return value + } + + @CachePut(value = DummyCache2::class, parameters = ["arg1", "arg2"]) + @CachePut(value = DummyCache22::class, parameters = ["arg1", "arg2"]) + open suspend fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { + delay(10) + return value + } + + @CacheInvalidate(value = DummyCache2::class) + @CacheInvalidate(value = DummyCache22::class) + open suspend fun evictValue(arg1: String?, arg2: BigDecimal?) { + delay(10) + } + + @CacheInvalidate(value = DummyCache2::class, invalidateAll = true) + @CacheInvalidate(value = DummyCache22::class, invalidateAll = true) + open suspend fun evictAll() { + delay(10) + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendOne.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendOne.kt new file mode 100644 index 000000000..147671e07 --- /dev/null +++ b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableSuspendOne.kt @@ -0,0 +1,34 @@ +package ru.tinkoff.kora.cache.symbol.processor.testdata.suspended + +import kotlinx.coroutines.delay +import ru.tinkoff.kora.cache.annotation.CacheInvalidate +import ru.tinkoff.kora.cache.annotation.CachePut +import ru.tinkoff.kora.cache.annotation.Cacheable +import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCache1 +import java.math.BigDecimal + +open class CacheableSuspendOne { + var value = "1" + + @Cacheable(value = DummyCache1::class) + open suspend fun getValue(arg1: String?): String { + delay(10) + return value + } + + @CachePut(value = DummyCache1::class, parameters = ["arg1"]) + open suspend fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { + delay(10) + return value + } + + @CacheInvalidate(value = DummyCache1::class) + open suspend fun evictValue(arg1: String?) { + delay(10) + } + + @CacheInvalidate(value = DummyCache1::class, invalidateAll = true) + open suspend fun evictAll() { + delay(10) + } +} diff --git a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspendMany.kt b/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspendMany.kt deleted file mode 100644 index 7add6b22f..000000000 --- a/cache/cache-symbol-processor/src/test/kotlin/ru/tinkoff/kora/cache/symbol/processor/testdata/suspended/CacheableTargetSuspendMany.kt +++ /dev/null @@ -1,38 +0,0 @@ -package ru.tinkoff.kora.cache.symbol.processor.testdata.suspended - -import kotlinx.coroutines.delay -import ru.tinkoff.kora.cache.annotation.CacheInvalidate -import ru.tinkoff.kora.cache.annotation.CachePut -import ru.tinkoff.kora.cache.annotation.Cacheable -import ru.tinkoff.kora.cache.symbol.processor.testcache.DummyCacheManager -import java.math.BigDecimal - -open class CacheableTargetSuspendMany { - var value = "1" - - @Cacheable(name = "suspend_cache", tags = [DummyCacheManager::class]) - @Cacheable(name = "suspend_cache_2", tags = [DummyCacheManager::class]) - open suspend fun getValue(arg1: String?, arg2: BigDecimal?): String { - delay(10) - return value - } - - @CachePut(name = "suspend_cache", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) - @CachePut(name = "suspend_cache_2", tags = [DummyCacheManager::class], parameters = ["arg1", "arg2"]) - open suspend fun putValue(arg2: BigDecimal?, arg3: String?, arg1: String?): String { - delay(10) - return value - } - - @CacheInvalidate(name = "suspend_cache", tags = [DummyCacheManager::class]) - @CacheInvalidate(name = "suspend_cache_2", tags = [DummyCacheManager::class]) - open suspend fun evictValue(arg1: String?, arg2: BigDecimal?) { - delay(10) - } - - @CacheInvalidate(name = "suspend_cache", tags = [DummyCacheManager::class], invalidateAll = true) - @CacheInvalidate(name = "suspend_cache_2", tags = [DummyCacheManager::class], invalidateAll = true) - open suspend fun evictAll() { - delay(10) - } -} diff --git a/database/database-common/build.gradle b/database/database-common/build.gradle index 7f2a8c2d5..37f0366dc 100644 --- a/database/database-common/build.gradle +++ b/database/database-common/build.gradle @@ -2,9 +2,8 @@ plugins { id "java-test-fixtures" } - dependencies { - compileOnly("org.jetbrains:annotations:23.0.0") + compileOnly libs.jetbrains.annotations api project(":common") api project(":logging:logging-common") diff --git a/dependencies.gradle b/dependencies.gradle index b1b2ae56d..39bea08e9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -30,6 +30,7 @@ dependencyResolutionManagement { DependencyResolutionManagement it -> library('micrometer-core', 'io.micrometer', 'micrometer-core').versionRef('micrometer') library('micrometer-registry-prometheus', 'io.micrometer', 'micrometer-registry-prometheus').versionRef('micrometer') library('prometheus-jmx-collector', 'io.prometheus.jmx', 'collector').version("0.17.2") + library('prometheus-collector-caffeine', 'io.prometheus', 'simpleclient_caffeine').version("0.16.0") library('opentelemetry-sdk-trace', 'io.opentelemetry', 'opentelemetry-sdk-trace').version('1.26.0') library('opentelemetry-context', 'io.opentelemetry', 'opentelemetry-context').version('1.26.0') @@ -50,7 +51,7 @@ dependencyResolutionManagement { DependencyResolutionManagement it -> library('kotlin-reflect', 'org.jetbrains.kotlin', 'kotlin-reflect').versionRef('kotlin_') library('kotlin-annotation-processing', 'org.jetbrains.kotlin', 'kotlin-annotation-processing-embeddable').versionRef('kotlin_') library('kotlin-compiler', 'org.jetbrains.kotlin', 'kotlin-compiler-embeddable').versionRef('kotlin_') - library('jetbrains.annotations', 'org.jetbrains', 'annotations').version('23.1.0') + library('jetbrains.annotations', 'org.jetbrains', 'annotations').version('24.0.1') library('kotlin-coroutines-core', 'org.jetbrains.kotlinx', 'kotlinx-coroutines-core').versionRef('kotlin-coroutines') library('kotlin-coroutines-reactor', 'org.jetbrains.kotlinx', 'kotlinx-coroutines-reactor').versionRef('kotlin-coroutines') @@ -103,6 +104,7 @@ dependencyResolutionManagement { DependencyResolutionManagement it -> library('caffeine', 'com.github.ben-manes.caffeine', 'caffeine').version('3.1.1') + library("awaitility", "org.awaitility", "awaitility").version("4.2.0") library("junit-jupiter", "org.junit.jupiter", "junit-jupiter").version("5.9.1") library("junit-platform-launcher", "org.junit.platform", "junit-platform-launcher").version("1.9.1") library("mockito-core", "org.mockito", "mockito-core").version("5.4.0") diff --git a/micrometer/micrometer-module/build.gradle b/micrometer/micrometer-module/build.gradle index 648cc7176..6362c044b 100644 --- a/micrometer/micrometer-module/build.gradle +++ b/micrometer/micrometer-module/build.gradle @@ -15,4 +15,6 @@ dependencies { compileOnly project(':scheduling:scheduling-common') compileOnly project(':resilient:resilient-kora') compileOnly project(':cache:cache-common') + compileOnly project(':cache:cache-caffeine') + compileOnly libs.prometheus.collector.caffeine } diff --git a/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/MetricsModule.java b/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/MetricsModule.java index de6ecd347..a97b5faa7 100644 --- a/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/MetricsModule.java +++ b/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/MetricsModule.java @@ -11,6 +11,7 @@ import io.micrometer.core.instrument.binder.system.UptimeMetrics; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; +import io.prometheus.client.cache.caffeine.CacheMetricsCollector; import ru.tinkoff.kora.application.graph.All; import ru.tinkoff.kora.application.graph.ValueOf; import ru.tinkoff.kora.common.DefaultComponent; @@ -158,4 +159,14 @@ default MicrometerTimeoutMetrics micrometerTimeoutMetrics(MeterRegistry meterReg default MicrometerCacheMetrics micrometerCacheMetrics(MeterRegistry meterRegistry) { return new MicrometerCacheMetrics(meterRegistry); } + + @DefaultComponent + default MicrometerCacheMetrics micrometerCaffeineCacheMetrics(MeterRegistry meterRegistry) { + return new MicrometerCacheMetrics(meterRegistry); + } + + @DefaultComponent + default CacheMetricsCollector cacheMetricsCollector() { + return new CacheMetricsCollector().register(); + } } diff --git a/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/cache/MicrometerCacheMetrics.java b/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/cache/MicrometerCacheMetrics.java index 333d51770..41eaf0505 100644 --- a/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/cache/MicrometerCacheMetrics.java +++ b/micrometer/micrometer-module/src/main/java/ru/tinkoff/kora/micrometer/module/cache/MicrometerCacheMetrics.java @@ -5,10 +5,11 @@ import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; import ru.tinkoff.kora.cache.telemetry.CacheMetrics; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; +import ru.tinkoff.kora.cache.telemetry.CacheTelemetryOperation; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.Collection; import java.util.concurrent.TimeUnit; public final class MicrometerCacheMetrics implements CacheMetrics { @@ -32,17 +33,17 @@ public MicrometerCacheMetrics(MeterRegistry meterRegistry) { } @Override - public void recordSuccess(@Nonnull CacheTelemetry.Operation operation, long durationInNanos, @Nullable Object valueFromCache) { + public void recordSuccess(@Nonnull CacheTelemetryOperation operation, long durationInNanos, @Nullable Object valueFromCache) { final Timer timer = meterRegistry.timer(METRIC_CACHE_DURATION, Tags.of( TAG_CACHE_NAME, operation.cacheName(), - TAG_OPERATION, operation.type().name(), + TAG_OPERATION, operation.name(), TAG_ORIGIN, operation.origin(), TAG_STATUS, STATUS_SUCCESS )); timer.record(durationInNanos, TimeUnit.NANOSECONDS); - if (CacheTelemetry.Operation.Type.GET == operation.type()) { - final String metricName = (valueFromCache == null) + if ("GET".startsWith(operation.name())) { + final String metricName = (valueFromCache == null || valueFromCache instanceof Collection vc && !vc.isEmpty()) ? METRIC_CACHE_MISS : METRIC_CACHE_HIT; @@ -55,10 +56,10 @@ public void recordSuccess(@Nonnull CacheTelemetry.Operation operation, long dura } @Override - public void recordFailure(@Nonnull CacheTelemetry.Operation operation, long durationInNanos, @Nullable Throwable throwable) { + public void recordFailure(@Nonnull CacheTelemetryOperation operation, long durationInNanos, @Nullable Throwable throwable) { final Timer timer = meterRegistry.timer(METRIC_CACHE_DURATION, Tags.of( TAG_CACHE_NAME, operation.cacheName(), - TAG_OPERATION, operation.type().name(), + TAG_OPERATION, operation.name(), TAG_ORIGIN, operation.origin(), TAG_STATUS, STATUS_FAILED )); diff --git a/mkdocs/docs/features/cache.md b/mkdocs/docs/features/cache.md index 8ea7bfd6d..9d3966d48 100644 --- a/mkdocs/docs/features/cache.md +++ b/mkdocs/docs/features/cache.md @@ -9,10 +9,49 @@ ## Examples +### Cache Implementation + +Для начала требуется зарегистрировать типизированный `Cache` сигнатуру. + +Интерфейс `Cache` должен наследоваться только от предоставляемых Kora'ой реализаций: `CaffeineCache` / `RedisCache`. + +Для такого `Cache` будет сгенерирована и добавлена в граф реализация, ее можно будет использовать как `Bean` для внедрения зависимостей. + +#### Single key + +В случае если ключ кэша представляет собой 1 аргумент, то требуется зарегистрировать `Cache` с сигнатурой соответствующей типам ключа и значения. + +```java +public interface DummyCache extends CaffeineCache { } +``` + +#### Composite key + +В случае если ключ кэша представляет собой N аргументов, то требуется зарегистрировать `Cache` с использованием `CacheKey` интерфейса который отвечает за композитный ключ, где типизированной сигнатурой указываются N аргументов ключа. + +Пример для `Cache` где композитный ключ состоит из 2 элементов: + +```java +public interface DummyCache extends CaffeineCache, String> { } +``` + +Для ключей из 3ых составляющих будет использоваться `CacheKey.Key3` и так далее. + +#### Config + +Для регистрации `Cache` и указания конфига требуется проаннотировать аннотацией `@Cache` где аргумент `value` означает полный путь к конфига. + +```java +@Cache("my.full.cache.config.path") +public interface DummyCache extends CaffeineCache, String> { } +``` + +### AOP + Допустим имеется класс: ```java @Component -public class CacheableTargetSync { +public class CacheExample { public Long getValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); @@ -32,7 +71,13 @@ public class CacheableTargetSync { } ``` -### Get +Для него регистрируем соответствующий `Cache`: +```java +@Cache("dummy") +public interface DummyCache extends CaffeineCache, String> { } +``` + +#### Get Для кэширования и получения значения из кэша для метода *getValue()* следует проаннотировать его аннотацией *@Cacheable*. @@ -42,15 +87,11 @@ public class CacheableTargetSync { Ключ для кэша составляет из аргументов метода, порядок аргументов имеет значение, в данном случае он будет составляться из значений *arg1* и *arg2*. -В *value* указывается имя кеша где будет храниться значения. -Место в каком кэше будет храниться значение, определяется его именем и его имплементацией. -Реализация указывается в *tags* либо используется дефолтная если подключен дефолтный модуль (пример модуля *DefaultCaffeineCacheModule*/*DefaultRedisCacheModule*). - ```java @Component -public class CacheableTargetSync { +public class CacheExample { - @Cacheable(name = "my_cache") + @Cacheable() public Long getValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } @@ -69,29 +110,24 @@ public class CacheableTargetSync { } ``` -### Put +#### Put Для добавления значений в кэш через метод *putValue()* следует проаннотировать его аннотацией *@CachePut*. -Метод проаннотированный *@CachePut* будет вызван и его значение положено во все кэш определенный в *value*, значение это значение будет возвращено дальше. -Имя кэша из *value* соответствует его имени в файле конфигурации (hocon) +Метод проаннотированный *@CachePut* будет вызван и его значение положено во все кэш определенный в *value*. Ключ для кэша составляет из аргументов метода, порядок аргументов имеет значение, в данном случае он будет составляться из значений *arg1* и *arg2*. -В *value* указывается имя кеша где будет храниться значения. -Место в каком кэше будет храниться значение, определяется его именем и его имплементацией. -Реализация указывается в *tags* либо используется дефолтная если подключен дефолтный модуль (пример модуля *DefaultCaffeineCacheModule*/*DefaultRedisCacheModule*). - ```java @Component -public class CacheableTargetSync { +public class CacheExample { - @Cacheable(name = "my_cache") + @Cacheable(DummyCache.class) public Long getValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } - @CachePut(name = "my_cache") + @CachePut(DummyCache.class) public Long putValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } @@ -106,34 +142,29 @@ public class CacheableTargetSync { } ``` -### Invalidate +#### Invalidate Для удаления значения по ключу из кэша через метод *evictValue()* следует проаннотировать его аннотацией *@CacheInvalidate*. Метод проаннотированный *@CacheInvalidate* будет вызван и затем по ключу для кэша определенного в *value* будут удалены значения по ключу. -Имя кэша из *value* соответствует его имени в файле конфигурации (hocon) Ключ для кэша составляет из аргументов метода, порядок аргументов имеет значение, в данном случае он будет составляться из значений *arg1* и *arg2*. -В *value* указывается имя кеша где будет храниться значения. -Место в каком кэше будет храниться значение, определяется его именем и его имплементацией. -Реализация указывается в *tags* либо используется дефолтная если подключен дефолтный модуль (пример модуля *DefaultCaffeineCacheModule*/*DefaultRedisCacheModule*). - ```java @Component -public class CacheableTargetSync { +public class CacheExample { - @Cacheable(name = "my_cache") + @Cacheable(DummyCache.class) public Long getValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } - @CachePut(name = "my_cache") + @CachePut(DummyCache.class) public Long putValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } - @CacheInvalidate(name = "my_cache") + @CacheInvalidate(DummyCache.class) public void evictValue(String arg1, BigDecimal arg2) { // do nothing } @@ -144,37 +175,32 @@ public class CacheableTargetSync { } ``` -#### InvalidateAll +##### InvalidateAll Для удаления всех значений из кэша через метод *evictAll()* следует проаннотировать его аннотацией *@CacheInvalidate* и указать параметр *invalidateAll = true*. Метод проаннотированный *@CacheInvalidate* будет вызван и затем будут удалены все из кэша определенного в *value*. -Имя кэша из *value* соответствует его имени в файле конфигурации (hocon) - -В *value* указывается имя кеша где будет храниться значения. -Место в каком кэше будет храниться значение, определяется его именем и его имплементацией. -Реализация указывается в *tags* либо используется дефолтная если подключен дефолтный модуль (пример модуля *DefaultCaffeineCacheModule*/*DefaultRedisCacheModule*). ```java @Component -public class CacheableTargetSync { +public class CacheExample { - @Cacheable(name = "my_cache") + @Cacheable(DummyCache.class) public Long getValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } - @CachePut(name = "my_cache") + @CachePut(DummyCache.class) public Long putValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } - @CacheInvalidate(name = "my_cache") + @CacheInvalidate(DummyCache.class) public void evictValue(String arg1, BigDecimal arg2) { // do nothing } - @CacheInvalidate(name = "my_cache", invalidateAll = true) + @CacheInvalidate(DummyCache.class, invalidateAll = true) public void evictAll() { // do nothing } @@ -189,22 +215,22 @@ public class CacheableTargetSync { @Component public class CacheableTargetMono { - @Cacheable(name = "my_cache") + @Cacheable(DummyCache.class) public Mono getValue(String arg1, BigDecimal arg2) { return Mono.just(ThreadLocalRandom.current().nextLong()); } - @CachePut(name = "my_cache") + @CachePut(DummyCache.class) public Mono putValue(String arg1, BigDecimal arg2) { return Mono.just(ThreadLocalRandom.current().nextLong()); } - @CacheInvalidate(name = "my_cache") + @CacheInvalidate(DummyCache.class) public Mono evictValue(String arg1, BigDecimal arg2) { return Mono.empty(); } - @CacheInvalidate(name = "my_cache", invalidateAll = true) + @CacheInvalidate(DummyCache.class, invalidateAll = true) public Mono evictAll() { return Mono.empty(); } @@ -219,20 +245,26 @@ public class CacheableTargetMono { Конфигурация будет выглядеть следующим образом: ```java @KoraApp -public interface ApplicationModules - extends RedisCacheModule, // Только размеченные в аннотации - DefaultCaffeineCacheModule { // Дефолтый кэш -} +public interface ApplicationModules extends RedisCacheModule, CaffeineCacheModule { } +``` + +Реализации: +```java +@Cache("my.caffeine.cache.config") +public interface CacheCaffeine extends CaffeineCache { } + +@Cache("my.redis.cache.config") +public interface CacheRedis extends RedisCache { } ``` А сам класс с кешами так: ```java @Component -public class CacheableTargetMono { +public class CacheableExample { - @Cacheable(name = "caffeine_cache") - @Cacheable(name = "redis_cache", tags = RedisCacheManager.class) - public Mono getValueCaffeine(String arg1, BigDecimal arg2) { + @Cacheable(CacheCaffeine.class) + @Cacheable(CacheRedis.class) + public Mono getValueCaffeine(String arg1) { return Mono.just(ThreadLocalRandom.current().nextLong()); } } @@ -251,14 +283,14 @@ public class CacheableTargetMono { ```java @Component -public class CacheableTargetSync { +public class CacheExample { - @Cacheable(name = "my_cache") + @Cacheable(DummyCache.class) public Long getValue(String arg1, BigDecimal arg2) { return ThreadLocalRandom.current().nextLong(); } - @Cacheable(name = "my_cache", parameters = {"arg1", "arg2"}) + @Cacheable(value = DummyCache.class, parameters = {"arg1", "arg2"}) public Long getValue(BigDecimal arg2, String arg3, String arg1) { return ThreadLocalRandom.current().nextLong(); } @@ -281,14 +313,13 @@ public class CacheableTargetSync { - `initialSize` - Начальный размер кэша (помогает избежать ресазинга в случае активного набухания кэша) *(Optional)* - `maximumSize` - Максимальный размер кэша (При достижении границы **или чуть ранее** будет исключать из кэша наименее актуальные значения) *(Optional)* -Предоставляет модули *DefaultCaffeineCacheModule* для использования Caffeine как дефолт реализацию кеша для приложения, -также *CaffeineCacheModule* для использования Caffeine кеша только где указаны соответсвующее теги в аннотации. +Предоставляет модуль *CaffeineCacheModule* для использования Caffeine. -Пример конфигурации для *my_cache* кэша: +Пример конфигурации для *my.cache.config* кэша: ```hocon -cache { - caffeine { - my_cache { +my { + cache { + config { expireAfterWrite = 10s expireAfterAccess = 10s initialSize = 10 @@ -298,7 +329,7 @@ cache { } ``` -### Dependencies +#### Dependencies **Java**: ```groovy @@ -312,13 +343,7 @@ ksp "ru.tinkoff.kora:annotation-processors" implementation "ru.tinkoff.kora:cache-caffeine" ``` -[Default Module](#несколько-кешей): -```java -@KoraApp -public interface ApplicationModules extends DefaultCaffeineCacheModule { } -``` - -Module для кеша с тэгом: +Module: ```java @KoraApp public interface ApplicationModules extends CaffeineCacheModule { } @@ -341,10 +366,9 @@ public interface ApplicationModules extends CaffeineCacheModule { } - expireAfterWrite - При записи устанавливает время [expiration](https://redis.io/commands/psetex/) - expireAfterAccess - При чтении устанавливает время [expiration](https://redis.io/commands/getex/) -Предоставляет модули *DefaultRedisCacheModule* для использования Redis как дефолт реализацию кеша для приложения, -также *RedisCacheModule* для использования Redis кеша только где указаны соответсвующее теги в аннотации. +Предоставляет модули *RedisCacheModule* для использования Redis. -Пример конфигурации для *my_cache* кэша. +Пример конфигурации для *my.cache.config* кэша. Требуется обязательно сконфигурировать клиент Lettuce для доступа в Redis. @@ -358,9 +382,10 @@ lettuce { socketTimeout = 15s commandTimeout = 15s } -cache { - redis { - my_cache { + +my { + cache { + config { expireAfterWrite = 10s expireAfterAccess = 10s } @@ -382,49 +407,43 @@ ksp "ru.tinkoff.kora:annotation-processors" implementation "ru.tinkoff.kora:cache-redis" ``` -[Default Module](#несколько-кешей): -```java -@KoraApp -public interface ApplicationModules extends DefaultRedisCacheModule { } -``` - -Module для кеша с тэгом: +Module: ```java @KoraApp public interface ApplicationModules extends RedisCacheModule { } ``` -### Loadable cache +### Loadable Cache Библиотека предоставляет компонент для построения сущности, которая объединяет операции GET и PUT, без использования AOP аннотаций - `LoadableCache` #### Кеширование блокирующих операций Иногда у нас есть долгая операция, которую бы мы хотели кешировать и запускать на отдельном потоке исполнения при использовании асинхронного апи. -Для создания такого `LoadableCache` можно воспользоваться фабричным методом `LoadableCache.blocking`, он создаёт такой cache, который при вызове `LoadableCache.getAsync` вызовет метод `load`, из примера ниже, на переданном `ExecutorService`. -При этом вызов `LoadableCache.get` вызовет 'load' на том же потоке +Для создания такого `CacheLoader` можно воспользоваться фабричным методом `CacheLoader.blocking`, он создаёт такой cache, который при вызове `CacheLoader.loadAsync` вызовет метод `load`, из примера ниже, на переданном `ExecutorService`. +При этом вызов `CacheLoader.load` вызовет 'load' на том же потоке ```java @Module -interface SomeEntityModule { - default LoadableCache someEntityLoadCache(CacheManager cacheManager, SomeService someService, ExecutorService executor) { - return LoadableCache.blocking(cacheManager.getCache("some.service"), executor, someService::loadEntity); +interface ApplicationModules { + default LoadableCache someEntityLoadCache(DummyCache cache, SomeService someService, ExecutorService executor) { + return LoadableCache.create(cache, CacheLoader.blocking(someService::loadEntity, executor)); } } @Component -class OtherService { - private final LoadableCache someEntityLoadableCache; +class ServiceExample { + private final LoadableCache loadableCache; - public OtherService(LoadableCache someEntityLoadableCache) { - this.someEntityLoadableCache = someEntityLoadableCache; + public OtherService(LoadableCache loadableCache) { + this.loadableCache = loadableCache; } } ``` #### Кеширование неблокирующих операций -По аналогии с методом `LoadableCache.blocking`, существуют фабричные методы `LoadableCache.nonBlocking` и `LoadableCache.async`, которые просто умеют кешировать результат переданной функции без +По аналогии с методом `CacheLoader.blocking`, существуют фабричные методы `CacheLoader.nonBlocking` и `CacheLoader.async`, которые просто умеют кешировать результат переданной функции без `Executor`. ## Supported Types diff --git a/opentelemetry/opentelemetry-module/src/main/java/ru/tinkoff/kora/opentelemetry/module/cache/OpentelementryCacheTracer.java b/opentelemetry/opentelemetry-module/src/main/java/ru/tinkoff/kora/opentelemetry/module/cache/OpentelementryCacheTracer.java index 819a62b61..51c3d2c96 100644 --- a/opentelemetry/opentelemetry-module/src/main/java/ru/tinkoff/kora/opentelemetry/module/cache/OpentelementryCacheTracer.java +++ b/opentelemetry/opentelemetry-module/src/main/java/ru/tinkoff/kora/opentelemetry/module/cache/OpentelementryCacheTracer.java @@ -4,7 +4,7 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; -import ru.tinkoff.kora.cache.telemetry.CacheTelemetry; +import ru.tinkoff.kora.cache.telemetry.CacheTelemetryOperation; import ru.tinkoff.kora.cache.telemetry.CacheTracer; import ru.tinkoff.kora.common.Context; import ru.tinkoff.kora.opentelemetry.common.OpentelemetryContext; @@ -42,13 +42,13 @@ public void recordFailure(@Nullable Throwable throwable) { } @Override - public CacheSpan trace(@Nonnull CacheTelemetry.Operation operation) { + public CacheSpan trace(@Nonnull CacheTelemetryOperation operation) { var context = Context.current(); var traceContext = OpentelemetryContext.get(context); var span = this.tracer.spanBuilder("cache.call") .setSpanKind(SpanKind.INTERNAL) .setParent(traceContext.getContext()) - .setAttribute(TAG_OPERATION, operation.type().name()) + .setAttribute(TAG_OPERATION, operation.name()) .setAttribute(TAG_CACHE_NAME, operation.cacheName()) .setAttribute(TAG_ORIGIN, operation.origin()) .startSpan(); diff --git a/resilient/resilient-kora/build.gradle b/resilient/resilient-kora/build.gradle index 501a887a4..887e25ce1 100644 --- a/resilient/resilient-kora/build.gradle +++ b/resilient/resilient-kora/build.gradle @@ -7,7 +7,7 @@ dependencies { testImplementation project(":internal:test-logging") testImplementation libs.testcontainers.junit.jupiter - testImplementation "org.awaitility:awaitility:4.2.0" + testImplementation libs.awaitility } -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle" diff --git a/resilient/resilient-symbol-processor/build.gradle b/resilient/resilient-symbol-processor/build.gradle index 3c7f1d29d..3d1a20a64 100644 --- a/resilient/resilient-symbol-processor/build.gradle +++ b/resilient/resilient-symbol-processor/build.gradle @@ -25,19 +25,4 @@ dependencies { } apply from: "${project.rootDir}/kotlin-plugin.gradle" -kotlin { - sourceSets { - testGenerated { - kotlin.srcDir("build/generated/ksp/sources/kotlin") - kotlin.srcDir("build/in-test-generated-ksp/ksp/sources/kotlin") - } - } - sourceSets.main { - kotlin.srcDir("build/generated/ksp/main/kotlin") - } - sourceSets.test { - kotlin.srcDir("build/generated/ksp/test/kotlin") - } -} - -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle" diff --git a/validation/validation-annotation-processor/build.gradle b/validation/validation-annotation-processor/build.gradle index 6835ba5c1..5fd9a3d4b 100644 --- a/validation/validation-annotation-processor/build.gradle +++ b/validation/validation-annotation-processor/build.gradle @@ -11,9 +11,6 @@ dependencies { testImplementation project(":kora-app-annotation-processor") testImplementation project(":config:config-annotation-processor") testImplementation project(":internal:test-logging") - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.2" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2" - testImplementation "org.junit.jupiter:junit-jupiter-params:5.8.2" } -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle" diff --git a/validation/validation-common/build.gradle b/validation/validation-common/build.gradle index 2875063d0..6bcdd88f3 100644 --- a/validation/validation-common/build.gradle +++ b/validation/validation-common/build.gradle @@ -1,10 +1,11 @@ dependencies { + compileOnly libs.jetbrains.annotations + api project(":common") - implementation project(":config:config-common") - implementation "org.jetbrains:annotations:23.0.0" + implementation project(":config:config-common") testImplementation project(":internal:test-logging") } -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle" diff --git a/validation/validation-symbol-processor/build.gradle b/validation/validation-symbol-processor/build.gradle index d3b69ee88..b1c11c7d9 100644 --- a/validation/validation-symbol-processor/build.gradle +++ b/validation/validation-symbol-processor/build.gradle @@ -25,19 +25,4 @@ dependencies { } apply from: "${project.rootDir}/kotlin-plugin.gradle" -kotlin { - sourceSets { - testGenerated { - kotlin.srcDir("build/generated/ksp/sources/kotlin") - kotlin.srcDir("build/in-test-generated-ksp/ksp/sources/kotlin") - } - } - sourceSets.main { - kotlin.srcDir("build/generated/ksp/main/kotlin") - } - sourceSets.test { - kotlin.srcDir("build/generated/ksp/test/kotlin") - } -} - -apply from: "../../in-test-generated.gradle" +apply from: "${project.rootDir}/in-test-generated.gradle"