├── config ├── checkstyle │ ├── .rsync-filter │ └── suppressions.xml ├── HEADER └── spotless.license.java ├── cache-bom └── build.gradle ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── license.gradle └── libs.versions.toml ├── src └── main │ └── docs │ └── guide │ ├── cacheGuides.adoc │ ├── repository.adoc │ ├── releaseHistory.adoc │ ├── microstream.adoc │ ├── control-panel.adoc │ ├── jcache.adoc │ ├── annotations │ └── conditional.adoc │ ├── redis.adoc │ ├── noop.adoc │ ├── introduction.adoc │ ├── cache-abstraction.adoc │ ├── toc.yml │ ├── annotations.adoc │ ├── caffeine.adoc │ └── hazelcast.adoc ├── cache-infinispan ├── src │ ├── test │ │ ├── resources │ │ │ ├── hotrod.properties │ │ │ └── logback.xml │ │ └── groovy │ │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ └── infinispan │ │ │ ├── InfinispanAsyncCacheSpec.groovy │ │ │ └── InfinispanSyncCacheSpec.groovy │ └── main │ │ └── java │ │ └── io │ │ └── micronaut │ │ └── cache │ │ └── infinispan │ │ ├── package-info.java │ │ ├── MicronautExecutorFactory.java │ │ ├── InfinispanCacheFactory.java │ │ ├── InfinispanSyncCache.java │ │ └── InfinispanCacheManager.java └── build.gradle ├── test-suite-caffeine-native ├── src │ └── test │ │ ├── java │ │ └── io │ │ │ └── micronaut │ │ │ ├── discovery │ │ │ └── client │ │ │ │ ├── package-info.java │ │ │ │ └── NativeDiscoveryTest.java │ │ │ └── cache │ │ │ ├── NewsController.java │ │ │ ├── RemovalListenerImpl.java │ │ │ ├── News.java │ │ │ ├── SimpleController.java │ │ │ ├── NewsControllerTest.java │ │ │ ├── CounterService.java │ │ │ ├── NativeCaffeineTest.java │ │ │ └── NewsService.java │ │ └── resources │ │ └── logback.xml └── build.gradle ├── .github ├── ISSUE_TEMPLATE │ ├── other.yaml │ ├── new_feature.yaml │ ├── config.yml │ └── bug_report.yaml ├── renovate.json ├── release.yml └── workflows │ ├── publish-snapshot.yml │ └── central-sync.yml ├── cache-noop ├── build.gradle └── src │ ├── test │ ├── resources │ │ └── logback.xml │ └── groovy │ │ └── io │ │ └── micronaut │ │ └── cache │ │ └── noop │ │ └── NoOpCacheConfigurationSpec.groovy │ └── main │ └── java │ └── io │ └── micronaut │ └── cache │ └── noop │ ├── NoOpCacheManager.java │ └── NoOpSyncCache.java ├── test-suite-caffeine-groovy ├── src │ └── test │ │ ├── groovy │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ ├── MyRemovalHandler.groovy │ │ │ ├── RemovalListenerImpl.groovy │ │ │ ├── SimpleController.groovy │ │ │ ├── ConditionalService.groovy │ │ │ ├── CounterService.groovy │ │ │ └── CaffeineTest.groovy │ │ └── resources │ │ └── logback.xml └── build.gradle.kts ├── test-suite-caffeine-kotlin ├── src │ └── test │ │ ├── kotlin │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ ├── MyRemovalHandler.kt │ │ │ ├── RemovalListenerImpl.kt │ │ │ ├── SimpleController.kt │ │ │ ├── ConditionalService.kt │ │ │ ├── CounterService.kt │ │ │ └── CaffeineTest.kt │ │ └── resources │ │ └── logback.xml └── build.gradle.kts ├── .gitattributes ├── cache-management ├── src │ ├── test │ │ ├── resources │ │ │ └── logback.xml │ │ └── groovy │ │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ └── management │ │ │ └── CachesEndpointConfigurationSpec.groovy │ └── main │ │ └── java │ │ └── io │ │ └── micronaut │ │ └── cache │ │ └── management │ │ └── package-info.java └── build.gradle ├── .editorconfig ├── cache-ehcache ├── build.gradle └── src │ ├── test │ ├── resources │ │ └── logback.xml │ └── groovy │ │ └── io │ │ └── micronaut │ │ └── cache │ │ └── ehcache │ │ ├── EhcacheSyncCacheSpec.groovy │ │ ├── EhcacheAsyncCacheSpec.groovy │ │ ├── EhcacheConfigurationSpec.groovy │ │ └── EhcacheCacheManagerFactorySpec.groovy │ └── main │ └── java │ └── io │ └── micronaut │ └── cache │ └── ehcache │ ├── package-info.java │ ├── configuration │ ├── AbstractResourcePoolConfiguration.java │ └── EhcacheClusterResourcePoolConfiguration.java │ └── serialization │ └── CharSequenceSerializer.java ├── test-suite-caffeine-java ├── src │ └── test │ │ ├── java │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ ├── MyRemovalHandler.java │ │ │ ├── RemovalListenerImpl.java │ │ │ ├── SimpleController.java │ │ │ ├── ConditionalService.java │ │ │ └── CounterService.java │ │ └── resources │ │ └── logback.xml └── build.gradle.kts ├── cache-core ├── src │ ├── test │ │ ├── resources │ │ │ └── logback.xml │ │ └── groovy │ │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ ├── serialize │ │ │ └── DefaultStringKeySerializerSpec.groovy │ │ │ └── CacheConfigWithoutCacheableSpec.groovy │ └── main │ │ └── java │ │ └── io │ │ └── micronaut │ │ └── cache │ │ ├── exceptions │ │ ├── package-info.java │ │ └── CacheSystemException.java │ │ ├── annotation │ │ ├── package-info.java │ │ ├── CacheAnnotation.java │ │ ├── PutOperations.java │ │ ├── InvalidateOperations.java │ │ └── CacheConfig.java │ │ ├── package-info.java │ │ ├── serialize │ │ └── package-info.java │ │ ├── interceptor │ │ ├── package-info.java │ │ ├── CacheKeyGenerator.java │ │ ├── ValueSupplierException.java │ │ ├── KotlinSuspendFunCacheKeyGenerator.java │ │ ├── DefaultCacheKeyGenerator.java │ │ └── ParametersKey.java │ │ ├── jcache │ │ └── package-info.java │ │ ├── discovery │ │ ├── package-info.java │ │ └── CachingCompositeDiscoveryClient.java │ │ ├── DefaultCacheErrorHandler.java │ │ ├── CacheInfo.java │ │ ├── Cache.java │ │ ├── DynamicCacheManager.java │ │ ├── CacheManager.java │ │ └── AsyncCacheErrorHandler.java └── build.gradle ├── .clineignore ├── test-suite-hazelcast-java ├── src │ └── test │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── io │ │ └── micronaut │ │ └── cache │ │ ├── HazelcastAdditionalSettings.java │ │ ├── SimpleController.java │ │ ├── CounterService.java │ │ └── HazelcastTest.java └── build.gradle.kts ├── test-suite-hazelcast-kotlin ├── src │ └── test │ │ ├── kotlin │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ ├── SimpleController.kt │ │ │ ├── HazelcastAdditionalSettings.kt │ │ │ ├── CounterService.kt │ │ │ └── HazelcastTest.kt │ │ └── resources │ │ └── logback.xml └── build.gradle.kts ├── test-suite-hazelcast-groovy ├── src │ └── test │ │ ├── resources │ │ └── logback.xml │ │ └── groovy │ │ └── io │ │ └── micronaut │ │ └── cache │ │ ├── HazelcastAdditionalSettings.groovy │ │ ├── SimpleController.groovy │ │ ├── CounterService.groovy │ │ └── HazelcastSpec.groovy └── build.gradle.kts ├── cache-hazelcast ├── build.gradle └── src │ ├── test │ ├── resources │ │ └── logback.xml │ └── groovy │ │ └── io │ │ └── micronaut │ │ └── cache │ │ └── hazelcast │ │ ├── HazelcastClientSupport.groovy │ │ ├── HazelcastClientSyncCacheSpec.groovy │ │ └── HazelcastMemberSyncCacheSpec.groovy │ └── main │ └── java │ └── io │ └── micronaut │ └── cache │ └── hazelcast │ ├── package-info.java │ ├── converters │ └── MapToConcurrentMapConverter.java │ └── HazelcastSyncCache.java ├── cache-caffeine ├── src │ ├── test │ │ ├── java │ │ │ └── io │ │ │ │ └── micronaut │ │ │ │ └── cache │ │ │ │ ├── RemovalListenerImpl.java │ │ │ │ ├── ConditionalService.java │ │ │ │ ├── DynamicCache.java │ │ │ │ └── CounterService.java │ │ ├── resources │ │ │ └── logback.xml │ │ └── groovy │ │ │ └── io │ │ │ └── micronaut │ │ │ └── cache │ │ │ ├── ConditionalCacheSpec.groovy │ │ │ ├── SyncCacheJavaSpec.groovy │ │ │ └── DynamicCacheSpec.groovy │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── native-image │ │ │ └── io.micronaut.cache │ │ │ └── micronaut-cache-caffeine │ │ │ └── native-image.properties │ │ └── java │ │ └── io │ │ └── micronaut │ │ └── cache │ │ └── caffeine │ │ ├── package-info.java │ │ └── DefaultCacheConfiguration.java └── build.gradle ├── SECURITY.md ├── cache-tck ├── build.gradle.kts └── src │ └── main │ └── groovy │ └── io │ └── micronaut │ └── cache │ └── tck │ └── PublisherService.groovy ├── .gitignore ├── ISSUE_TEMPLATE.md ├── settings.gradle ├── gradle.properties ├── CONTRIBUTING.md └── README.md /config/checkstyle/.rsync-filter: -------------------------------------------------------------------------------- 1 | - suppressions.xml 2 | - checkstyle.xml 3 | -------------------------------------------------------------------------------- /cache-bom/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.bom' 3 | } 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/micronaut-projects/micronaut-cache/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/docs/guide/cacheGuides.adoc: -------------------------------------------------------------------------------- 1 | See the guide for https://guides.micronaut.io/latest/micronaut-cache.html[Micronaut Cache] to learn more. 2 | -------------------------------------------------------------------------------- /cache-infinispan/src/test/resources/hotrod.properties: -------------------------------------------------------------------------------- 1 | infinispan.client.hotrod.server_list = localhost:11224;localhost:11225 2 | infinispan.client.hotrod.statistics = true -------------------------------------------------------------------------------- /src/main/docs/guide/repository.adoc: -------------------------------------------------------------------------------- 1 | You can find the source code of this project in this repository: 2 | 3 | https://github.com/{githubSlug}[https://github.com/{githubSlug}] -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/discovery/client/package-info.java: -------------------------------------------------------------------------------- 1 | @Configuration 2 | package io.micronaut.discovery.client; 3 | 4 | import io.micronaut.context.annotation.Configuration; 5 | -------------------------------------------------------------------------------- /src/main/docs/guide/releaseHistory.adoc: -------------------------------------------------------------------------------- 1 | For this project, you can find a list of releases (with release notes) here: 2 | 3 | https://github.com/{githubSlug}/releases[https://github.com/{githubSlug}/releases] 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yaml: -------------------------------------------------------------------------------- 1 | name: Other 2 | description: Something different 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Issue description 7 | validations: 8 | required: true 9 | 10 | -------------------------------------------------------------------------------- /cache-noop/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | api projects.micronautCacheCore 7 | 8 | testImplementation(mn.micronaut.inject.groovy) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/docs/guide/microstream.adoc: -------------------------------------------------------------------------------- 1 | To use https://microstream.one/[MicroStream] as the caching implementation, https://micronaut-projects.github.io/micronaut-microstream/snapshot/guide/#cache[set up the Micronaut MicroStream module]. 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/docs/guide/control-panel.adoc: -------------------------------------------------------------------------------- 1 | The Micronaut Control Panel module has support for Micronaut Cache by adding the following dependency: 2 | 3 | dependency:io.micronaut.controlpanel:micronaut-control-panel-cache[scope=developmentOnly] 4 | 5 | Check 6 | https://micronaut-projects.github.io/micronaut-control-panel/latest/guide/#cache[the documentation] for more information 7 | -------------------------------------------------------------------------------- /src/main/docs/guide/jcache.adoc: -------------------------------------------------------------------------------- 1 | When there is a JSR 107 (JCache) implementation in the classpath (Ehcache, Hazelcast, Infinispan, etc), the caching 2 | abstraction will use the JCache API internally by default. If you want Micronaut to use the concrete implementation API, 3 | JCache needs to be disabled: 4 | 5 | [configuration] 6 | ---- 7 | micronaut: 8 | jcache: 9 | enabled: false 10 | ---- 11 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/groovy/io/micronaut/cache/MyRemovalHandler.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause 4 | import jakarta.inject.Singleton 5 | 6 | @Singleton 7 | class MyRemovalHandler { 8 | 9 | List removals = [] 10 | 11 | void handle(String key, Integer value, RemovalCause cause) { 12 | removals << "$key|$value|$cause" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/kotlin/io/micronaut/cache/MyRemovalHandler.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause 4 | import jakarta.inject.Singleton 5 | 6 | @Singleton 7 | class MyRemovalHandler { 8 | 9 | val removals: MutableList = ArrayList() 10 | 11 | fun handle(key: String, value: Int, cause: RemovalCause) { 12 | removals.add("$key|$value|$cause") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/docs/guide/annotations/conditional.adoc: -------------------------------------------------------------------------------- 1 | Since Micronaut Cache 4.2.0, the above annotations can be conditionally disabled via an https://docs.micronaut.io/latest/guide/#evaluatedExpressions[Expression Language] expression in the `condition` attribute. 2 | 3 | For example, we can cache the result of a method invocation only if the `id` parameters value is greater than 5: 4 | 5 | snippet::io.micronaut.cache.ConditionalService[project-base="test-suite-caffeine",tags="conditional",indent=0] 6 | -------------------------------------------------------------------------------- /config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/docs/guide/redis.adoc: -------------------------------------------------------------------------------- 1 | [TIP] 2 | .Using the CLI 3 | ==== 4 | If you are creating your project using the Micronaut CLI, supply the `redis-lettuce` feature to configure Redis/Lettuce in your project: 5 | ---- 6 | $ mn create-app my-app --features redis-lettuce 7 | ---- 8 | ==== 9 | 10 | If you wish to use Redis to cache results the https://micronaut-projects.github.io/micronaut-redis/snapshot/guide/#cache[Micronaut Redis] module provides a api:cache.CacheManager[] implementation that allows using Redis as a backing cache. -------------------------------------------------------------------------------- /src/main/docs/guide/noop.adoc: -------------------------------------------------------------------------------- 1 | Dependent on the environment or when testing it might be undesirable to actually cache items. 2 | In such situations a no operation cache manager can be used that will simply accept any items into the cache without actually storing them. 3 | 4 | Add the Micronaut no operation cache module as a dependency: 5 | 6 | dependency:io.micronaut.cache:micronaut-cache-noop[] 7 | 8 | The no operation cache manager needs to be enabled explicitly: 9 | 10 | [configuration] 11 | ---- 12 | noop-cache.enabled: true 13 | ---- 14 | -------------------------------------------------------------------------------- /src/main/docs/guide/introduction.adoc: -------------------------------------------------------------------------------- 1 | This project brings additional cache implementations to Micronaut. 2 | 3 | To get started, you need to declare the following dependency: 4 | 5 | dependency:io.micronaut.cache:micronaut-cache-core[] 6 | 7 | NOTE: The configuration implementations in this module require at least Micronaut version 1.3.0. Each implementation is a separate dependency. 8 | 9 | To use the `BUILD-SNAPSHOT` version of this library, check the 10 | https://docs.micronaut.io/latest/guide/index.html#usingsnapshots[documentation to use snapshots]. 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Create a new feature request 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Please describe the feature you want for Micronaut to implement, before that check if there is already an existing issue to add it. 8 | - type: textarea 9 | attributes: 10 | label: Feature description 11 | placeholder: Tell us what feature you would like for Micronaut to have and what problem is it going to solve 12 | validations: 13 | required: true 14 | 15 | -------------------------------------------------------------------------------- /src/main/docs/guide/cache-abstraction.adoc: -------------------------------------------------------------------------------- 1 | Similar to Spring and Grails, Micronaut provides a set of caching annotations within the link:{api}/io/micronaut/cache/package-summary.html[io.micronaut.cache] package. 2 | 3 | The link:{api}/io/micronaut/cache/CacheManager.html[CacheManager] interface allows different cache implementations to be plugged in as necessary. 4 | 5 | The link:{api}/io/micronaut/cache/SyncCache.html[SyncCache] interface provides a synchronous API for caching, whilst the link:{api}/io/micronaut/cache/AsyncCache.html[AsyncCache] API allows non-blocking operation. 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/docs/guide/toc.yml: -------------------------------------------------------------------------------- 1 | introduction: Introduction 2 | releaseHistory: Release History 3 | cache-abstraction: Cache Abstraction 4 | annotations: 5 | title: Cache Annotations 6 | conditional: Conditional Caching 7 | caffeine: Caching with Caffeine 8 | jcache: JCache API support 9 | redis: Redis Support 10 | ehcache: Ehcache Support 11 | hazelcast: Hazelcast Support 12 | infinispan: Infinispan Support 13 | microstream: MicroStream Support 14 | noop: No Operation Cache Support 15 | endpoint: Endpoint 16 | control-panel: Control Panel 17 | cacheGuides: Guides 18 | repository: Repository 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | *.java text eol=lf 5 | *.groovy text eol=lf 6 | *.html text eol=lf 7 | *.kt text eol=lf 8 | *.kts text eol=lf 9 | *.md text diff=markdown eol=lf 10 | *.py text diff=python executable 11 | *.pl text diff=perl executable 12 | *.pm text diff=perl 13 | *.css text diff=css eol=lf 14 | *.js text eol=lf 15 | *.sql text eol=lf 16 | *.q text eol=lf 17 | 18 | *.sh text eol=lf 19 | gradlew text eol=lf 20 | 21 | *.bat text eol=crlf 22 | *.cmd text eol=crlf 23 | -------------------------------------------------------------------------------- /cache-management/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/kotlin/io/micronaut/cache/RemovalListenerImpl.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause 4 | import com.github.benmanes.caffeine.cache.RemovalListener 5 | import jakarta.inject.Singleton 6 | 7 | // tag::clazz[] 8 | @Singleton 9 | class RemovalListenerImpl internal constructor(private val handler: MyRemovalHandler) : RemovalListener { 10 | 11 | override fun onRemoval(key: String?, value: Int?, cause: RemovalCause) { 12 | handler.handle(key!!, value!!, cause) 13 | } 14 | } 15 | // end::clazz[] 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | 9 | [{*.sh,gradlew}] 10 | end_of_line = lf 11 | 12 | [{*.bat,*.cmd}] 13 | end_of_line = crlf 14 | 15 | [{*.mustache,*.ftl}] 16 | insert_final_newline = false 17 | 18 | [*.java] 19 | indent_size = 4 20 | tab_width = 4 21 | max_line_length = 100 22 | # Import order can be configured with ij_java_imports_layout=... 23 | # See documentation https://youtrack.jetbrains.com/issue/IDEA-170643#focus=streamItem-27-3708697.0-0 24 | 25 | [*.xml] 26 | indent_size = 4 27 | -------------------------------------------------------------------------------- /config/HEADER: -------------------------------------------------------------------------------- 1 | Copyright ${year} original authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Micronaut Core Discussions 3 | url: https://github.com/micronaut-projects/micronaut-core/discussions 4 | about: Ask questions about Micronaut on Github 5 | - name: Micronaut Data Discussions 6 | url: https://github.com/micronaut-projects/micronaut-data/discussions 7 | about: Ask Micronaut Data related questions on Github 8 | - name: Stack Overflow 9 | url: https://stackoverflow.com/tags/micronaut 10 | about: Ask questions on Stack Overflow 11 | - name: Chat 12 | url: https://gitter.im/micronautfw/ 13 | about: Chat with us on Gitter. -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/NewsController.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.http.annotation.Controller; 4 | import io.micronaut.http.annotation.Get; 5 | 6 | import java.time.Month; 7 | 8 | @Controller 9 | public class NewsController { 10 | 11 | private final NewsService newsService; 12 | 13 | public NewsController(NewsService newsService) { 14 | this.newsService = newsService; 15 | } 16 | 17 | @Get("/{month}") 18 | public News index(Month month) { 19 | return new News(month, newsService.headlines(month)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cache-ehcache/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | api projects.micronautCacheCore 7 | api(libs.managed.ehcache.clustered) 8 | api(libs.managed.ehcache) 9 | 10 | testImplementation projects.micronautCacheTck 11 | testImplementation(mn.micronaut.http.client) 12 | testImplementation(mn.micronaut.inject.groovy) 13 | testImplementation(mn.reactor) 14 | testImplementation(platform(mnTestResources.boms.testcontainers)) 15 | testImplementation(libs.testcontainers.spock) 16 | testRuntimeOnly(mnTest.junit.platform.suite) 17 | } 18 | -------------------------------------------------------------------------------- /test-suite-caffeine-java/src/test/java/io/micronaut/cache/MyRemovalHandler.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause; 4 | import jakarta.inject.Singleton; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Singleton 10 | public class MyRemovalHandler { 11 | 12 | private List removals = new ArrayList<>(); 13 | 14 | void handle(String key, Integer value, RemovalCause cause) { 15 | removals.add(key + "|" + value + "|" + cause); 16 | } 17 | 18 | public List getRemovals() { 19 | return removals; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cache-core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /cache-noop/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /gradle/license.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.hierynomus.license' 2 | 3 | license { 4 | header = rootProject.file('config/HEADER') 5 | strictCheck = true 6 | ignoreFailures = true 7 | mapping { 8 | kt = 'SLASHSTAR_STYLE' 9 | java = 'SLASHSTAR_STYLE' 10 | groovy = 'SLASHSTAR_STYLE' 11 | } 12 | ext.year = '2017-2020' 13 | 14 | exclude "**/transaction/**" 15 | exclude '**/*.txt' 16 | exclude '**/*.html' 17 | exclude '**/*.xml' 18 | exclude '**/*.json' 19 | exclude '**/build-info.properties' 20 | exclude '**/git.properties' 21 | exclude '**/othergit.properties' 22 | } 23 | -------------------------------------------------------------------------------- /cache-infinispan/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.clineignore: -------------------------------------------------------------------------------- 1 | # .clineignore - Cline AI ignore file for Micronaut projects 2 | # This file tells Cline which files/directories to ignore for code intelligence and automation 3 | 4 | # === Build outputs === 5 | build/ 6 | */build/ 7 | !build/docs/ 8 | !build/docs/** 9 | !build/generated/ 10 | !build/generated/** 11 | 12 | # === Dependency/Cache directories === 13 | .gradle/ 14 | */.gradle/ 15 | 16 | # === IDE/Editor/OS Metadata === 17 | .vscode/ 18 | .idea/ 19 | .DS_Store 20 | *.swp 21 | *.swo 22 | *.bak 23 | *.tmp 24 | *.orig 25 | 26 | # === Tool-specific/Config artifacts === 27 | *.log 28 | 29 | # === Gradle Wrappers/Binaries === 30 | gradlew 31 | gradlew.bat 32 | -------------------------------------------------------------------------------- /cache-core/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | api(mn.micronaut.aop) 7 | api(mn.micronaut.http) 8 | api(mn.micronaut.inject) 9 | 10 | compileOnly(mnMicrometer.micronaut.micrometer.core) 11 | compileOnly(mn.micronaut.management) 12 | compileOnly(libs.cache.api) 13 | 14 | implementation(mn.reactor) 15 | 16 | testImplementation(mnLogging.logback.classic) 17 | testImplementation(mn.micronaut.http.client) 18 | testImplementation(mn.micronaut.inject.groovy) 19 | testImplementation(libs.cache.api) 20 | testImplementation(libs.cache.ri.impl) 21 | } 22 | -------------------------------------------------------------------------------- /cache-management/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | annotationProcessor(mnValidation.micronaut.validation.processor) 7 | api projects.micronautCacheCore 8 | api(mn.micronaut.management) 9 | 10 | implementation(mn.reactor) 11 | implementation(mnValidation.micronaut.validation) 12 | 13 | testImplementation projects.micronautCacheCaffeine 14 | testImplementation(mn.micronaut.http.client) 15 | testImplementation(mn.micronaut.http.server.netty) 16 | testImplementation(mn.micronaut.management) 17 | testRuntimeOnly(mnSerde.micronaut.serde.jackson) 18 | } 19 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-caffeine-java/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/kotlin/io/micronaut/cache/SimpleController.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.http.HttpStatus 4 | import io.micronaut.http.annotation.Controller 5 | import io.micronaut.http.annotation.Get 6 | import io.micronaut.http.annotation.Status 7 | 8 | @Controller 9 | class SimpleController(private val counterService: CounterService) { 10 | 11 | @Get("/inc") 12 | fun increment() = counterService.increment("test") 13 | 14 | @Get("/get") 15 | fun get() = counterService.getValue("test") 16 | 17 | @Get("/del") 18 | @Status(HttpStatus.FOUND) 19 | fun del() = counterService.reset("test") 20 | } 21 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-hazelcast-java/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-hazelcast-kotlin/src/test/kotlin/io/micronaut/cache/SimpleController.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.http.HttpStatus 4 | import io.micronaut.http.annotation.Controller 5 | import io.micronaut.http.annotation.Get 6 | import io.micronaut.http.annotation.Status 7 | 8 | @Controller 9 | class SimpleController(private val counterService: CounterService) { 10 | 11 | @Get("/inc") 12 | fun increment() = counterService.increment("test") 13 | 14 | @Get("/get") 15 | fun get() = counterService.getValue("test") 16 | 17 | @Get("/del") 18 | @Status(HttpStatus.FOUND) 19 | fun del() = counterService.reset("test") 20 | } 21 | -------------------------------------------------------------------------------- /test-suite-hazelcast-groovy/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-hazelcast-kotlin/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /config/spotless.license.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-$YEAR original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /test-suite-caffeine-java/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.cache-test-suite") 3 | id("java-library") 4 | } 5 | 6 | dependencies { 7 | testAnnotationProcessor(mn.micronaut.inject.java) 8 | 9 | testImplementation(projects.micronautCacheCaffeine) 10 | 11 | testImplementation(mn.micronaut.http.client) 12 | testImplementation(mn.micronaut.http.server.netty) 13 | testImplementation(mnSerde.micronaut.serde.jackson) 14 | testImplementation(mnTest.micronaut.test.junit5) 15 | 16 | testRuntimeOnly(mnTest.junit.jupiter.engine) 17 | testRuntimeOnly(mnLogging.logback.classic) 18 | testRuntimeOnly(mnTest.junit.platform.suite) 19 | } 20 | -------------------------------------------------------------------------------- /cache-ehcache/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.cache-test-suite-kotlin") 3 | } 4 | 5 | dependencies { 6 | kaptTest(mn.micronaut.inject.java) 7 | 8 | testImplementation(projects.micronautCacheCaffeine) 9 | 10 | testImplementation(mn.micronaut.http.client) 11 | testImplementation(mn.micronaut.http.server.netty) 12 | testImplementation(mn.kotlinx.coroutines.core) 13 | testImplementation(mnSerde.micronaut.serde.jackson) 14 | testImplementation(mnTest.micronaut.test.junit5) 15 | 16 | testRuntimeOnly(mnTest.junit.jupiter.engine) 17 | testRuntimeOnly(mnLogging.logback.classic) 18 | testRuntimeOnly(mnTest.junit.platform.suite) 19 | } 20 | -------------------------------------------------------------------------------- /test-suite-hazelcast-kotlin/src/test/kotlin/io/micronaut/cache/HazelcastAdditionalSettings.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.hazelcast.HazelcastClientConfiguration 4 | import io.micronaut.context.event.BeanCreatedEvent 5 | import io.micronaut.context.event.BeanCreatedEventListener 6 | import jakarta.inject.Singleton 7 | 8 | // tag::clazz[] 9 | @Singleton 10 | class HazelcastAdditionalSettings : BeanCreatedEventListener { 11 | 12 | override fun onCreated(event: BeanCreatedEvent) = event.bean.apply { 13 | // Set anything on the configuration 14 | clusterName = "dev" 15 | } 16 | } 17 | // end::clazz[] 18 | -------------------------------------------------------------------------------- /cache-hazelcast/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | api projects.micronautCacheCore 7 | api(libs.managed.hazelcast) 8 | 9 | testAnnotationProcessor(mn.micronaut.inject.java) 10 | 11 | testImplementation(projects.micronautCacheTck) 12 | testImplementation(mn.micronaut.http.client) 13 | testImplementation(mn.micronaut.inject.groovy) 14 | testImplementation(mn.reactor) 15 | testImplementation(platform(mnTestResources.boms.testcontainers)) 16 | testImplementation(libs.testcontainers.spock) 17 | } 18 | 19 | tasks.withType(Test) { 20 | systemProperty('hazelcastVersion', libs.versions.managed.hazelcast.get()) 21 | } 22 | -------------------------------------------------------------------------------- /cache-infinispan/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | api projects.micronautCacheCore 7 | 8 | api(libs.managed.infinispan.core) 9 | api(libs.managed.infinispan.client.hotrod) 10 | 11 | testImplementation projects.micronautCacheTck 12 | testImplementation(mn.micronaut.http.client) 13 | testImplementation(mn.micronaut.inject.groovy) 14 | testImplementation(mn.reactor) 15 | testImplementation(platform(mnTestResources.boms.testcontainers)) 16 | testImplementation(libs.testcontainers.spock) 17 | } 18 | 19 | tasks.withType(Test) { 20 | systemProperty('infinispanVersion', libs.versions.managed.infinispan.get()) 21 | } 22 | -------------------------------------------------------------------------------- /cache-hazelcast/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/java/io/micronaut/cache/RemovalListenerImpl.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause; 4 | import com.github.benmanes.caffeine.cache.RemovalListener; 5 | import org.jspecify.annotations.NonNull; 6 | import org.jspecify.annotations.Nullable; 7 | import jakarta.inject.Singleton; 8 | 9 | @Singleton 10 | public class RemovalListenerImpl implements RemovalListener { 11 | 12 | private boolean removed = false; 13 | 14 | @Override 15 | public void onRemoval(@Nullable String key, @Nullable Integer value, @NonNull RemovalCause cause) { 16 | removed = true; 17 | } 18 | 19 | public boolean isRemoved() { 20 | return removed; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended" 4 | ], 5 | "addLabels": [ 6 | "type: dependency-upgrade" 7 | ], 8 | "schedule": [ 9 | "after 10pm" 10 | ], 11 | "prHourlyLimit": 1, 12 | "prConcurrentLimit": 20, 13 | "timezone": "Europe/Prague", 14 | "packageRules": [ 15 | { 16 | "dependencyDashboardApproval": true, 17 | "matchUpdateTypes": [ 18 | "patch" 19 | ], 20 | "matchCurrentVersion": "!/^0/", 21 | "automerge": true, 22 | "matchPackageNames": [ 23 | "/actions.*/" 24 | ] 25 | }, 26 | { 27 | "matchUpdateTypes": [ 28 | "patch" 29 | ], 30 | "matchCurrentVersion": "!/^0/", 31 | "automerge": true 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | We release patches for security vulnerabilities. Which versions are eligible 4 | receiving such patches depend on the CVSS v3.0 Rating: 5 | 6 | | CVSS v3.0 | Supported Versions | 7 | |-----------|-------------------------------------------| 8 | | 9.0-10.0 | Releases within the previous three months | 9 | | 4.0-8.9 | Most recent release | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please responsibly disclose (suspected) security vulnerabilities to 14 | **[The Micronaut Foundation](foundation@micronaut.io)**. You will receive a response from 15 | us within 48 hours. If the issue is confirmed, we will release a patch as soon 16 | as possible depending on complexity but historically within a few days. 17 | -------------------------------------------------------------------------------- /cache-caffeine/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.micronaut.build.internal.cache-module' 3 | } 4 | 5 | dependencies { 6 | api projects.micronautCacheCore 7 | api(libs.managed.caffeine) 8 | 9 | compileOnly(mnMicrometer.micronaut.micrometer.core) 10 | compileOnly(libs.graal.svm) 11 | 12 | implementation(mn.reactor) 13 | 14 | testAnnotationProcessor(mn.micronaut.inject.java) 15 | 16 | testImplementation(mnSerde.micronaut.serde.jackson) 17 | testImplementation(mnLogging.logback.classic) 18 | testImplementation(libs.cache.ri.impl) 19 | testImplementation(mn.micronaut.management) 20 | testImplementation(mn.micronaut.http.client) 21 | testImplementation(mn.micronaut.http.server.netty) 22 | testImplementation(mnMicrometer.micronaut.micrometer.core) 23 | } 24 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /cache-caffeine/src/main/resources/META-INF/native-image/io.micronaut.cache/micronaut-cache-caffeine/native-image.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017-2021 original authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | Args = --features=io.micronaut.cache.caffeine.graal.CaffeineFeature 17 | -------------------------------------------------------------------------------- /test-suite-hazelcast-groovy/src/test/groovy/io/micronaut/cache/HazelcastAdditionalSettings.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.hazelcast.HazelcastClientConfiguration 4 | import io.micronaut.context.event.BeanCreatedEvent 5 | import io.micronaut.context.event.BeanCreatedEventListener 6 | import org.jspecify.annotations.NonNull 7 | import jakarta.inject.Singleton 8 | 9 | // tag::clazz[] 10 | @Singleton 11 | class HazelcastAdditionalSettings implements BeanCreatedEventListener { 12 | 13 | @Override 14 | HazelcastClientConfiguration onCreated(@NonNull BeanCreatedEvent event) { 15 | event.bean.tap { 16 | // Set anything on the configuration 17 | clusterName = "dev" 18 | } 19 | } 20 | } 21 | // end::clazz[] 22 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/groovy/io/micronaut/cache/RemovalListenerImpl.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause 4 | import com.github.benmanes.caffeine.cache.RemovalListener 5 | import org.jspecify.annotations.NonNull 6 | import org.jspecify.annotations.Nullable 7 | import jakarta.inject.Singleton 8 | 9 | // tag::clazz[] 10 | @Singleton 11 | class RemovalListenerImpl implements RemovalListener { 12 | 13 | private final MyRemovalHandler handler 14 | 15 | RemovalListenerImpl(MyRemovalHandler handler) { 16 | this.handler = handler 17 | } 18 | 19 | @Override 20 | public void onRemoval(@Nullable String key, @Nullable Integer value, @NonNull RemovalCause cause) { 21 | handler.handle(key, value, cause) 22 | } 23 | } 24 | // end::clazz[] 25 | -------------------------------------------------------------------------------- /test-suite-caffeine-java/src/test/java/io/micronaut/cache/RemovalListenerImpl.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause; 4 | import com.github.benmanes.caffeine.cache.RemovalListener; 5 | import org.jspecify.annotations.NonNull; 6 | import org.jspecify.annotations.Nullable; 7 | import jakarta.inject.Singleton; 8 | 9 | // tag::clazz[] 10 | @Singleton 11 | public class RemovalListenerImpl implements RemovalListener { 12 | 13 | private final MyRemovalHandler handler; 14 | 15 | RemovalListenerImpl(MyRemovalHandler handler) { 16 | this.handler = handler; 17 | } 18 | 19 | @Override 20 | public void onRemoval(@Nullable String key, @Nullable Integer value, @NonNull RemovalCause cause) { 21 | handler.handle(key, value, cause); 22 | } 23 | } 24 | // end::clazz[] 25 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/exceptions/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains cache exceptions. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | package io.micronaut.cache.exceptions; 23 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - micronaut-build 5 | categories: 6 | - title: Breaking Changes 🛠 7 | labels: 8 | - 'type: breaking' 9 | - title: New Features 🎉 10 | labels: 11 | - 'type: enhancement' 12 | - title: Bug Fixes 🐞 13 | labels: 14 | - 'type: bug' 15 | - title: Improvements ⭐ 16 | labels: 17 | - 'type: improvement' 18 | - title: Docs 📖 19 | labels: 20 | - 'type: docs' 21 | - title: Dependency updates 🚀 22 | labels: 23 | - 'type: dependency-upgrade' 24 | - 'dependency-upgrade' 25 | - title: Regressions 🧐 26 | labels: 27 | - 'type: regression' 28 | - title: GraalVM 🏆 29 | labels: 30 | - 'relates-to: graal' 31 | - title: Other Changes 💡 32 | labels: 33 | - "*" 34 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/kotlin/io/micronaut/cache/ConditionalService.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.annotation.CacheConfig 4 | import io.micronaut.cache.annotation.Cacheable 5 | import jakarta.inject.Singleton 6 | 7 | @Singleton 8 | @CacheConfig(cacheNames = ["conditional"]) 9 | open class ConditionalService { 10 | 11 | private val repository = DocRepo() 12 | 13 | // tag::conditional[] 14 | data class Id(val value: Int) 15 | 16 | @Cacheable(condition = "#{id.value > 5}") 17 | open fun get(id: Id) = repository[id] 18 | // end::conditional[] 19 | 20 | // here to make the docs look nice when we extract the function above 21 | private class DocRepo { 22 | operator fun get(id: Id): String { 23 | return "test " + id.value + " " + System.currentTimeMillis() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test-suite-hazelcast-groovy/src/test/groovy/io/micronaut/cache/SimpleController.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.http.HttpStatus 4 | import io.micronaut.http.annotation.Controller 5 | import io.micronaut.http.annotation.Get 6 | import io.micronaut.http.annotation.Status 7 | 8 | @Controller 9 | class SimpleController { 10 | 11 | private final CounterService counterService 12 | 13 | SimpleController(CounterService counterService) { 14 | this.counterService = counterService 15 | } 16 | 17 | @Get("/inc") 18 | int increment() { 19 | counterService.increment("test") 20 | } 21 | 22 | @Get("/get") 23 | int get() { 24 | counterService.getValue("test") 25 | } 26 | 27 | @Get("/del") 28 | @Status(HttpStatus.FOUND) 29 | void del() { 30 | counterService.reset("test") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains cache annotations in Micronaut. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | package io.micronaut.cache.annotation; 23 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains cache interfaces and implementations in Micronaut. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | package io.micronaut.cache; 23 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/serialize/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains cache serializers in Micronaut. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | package io.micronaut.cache.serialize; 23 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/RemovalListenerImpl.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import com.github.benmanes.caffeine.cache.RemovalCause; 4 | import com.github.benmanes.caffeine.cache.RemovalListener; 5 | import org.jspecify.annotations.NonNull; 6 | import org.jspecify.annotations.Nullable; 7 | import jakarta.inject.Singleton; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Singleton 12 | public class RemovalListenerImpl implements RemovalListener { 13 | 14 | private List removals = new ArrayList<>(); 15 | 16 | @Override 17 | public void onRemoval(@Nullable String key, @Nullable Integer value, @NonNull RemovalCause cause) { 18 | removals.add(key + "|" + value + "|" + cause); 19 | } 20 | 21 | public List getRemovals() { 22 | return removals; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/interceptor/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains cache interceptors in Micronaut. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | package io.micronaut.cache.interceptor; 23 | -------------------------------------------------------------------------------- /cache-caffeine/src/main/java/io/micronaut/cache/caffeine/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains a cache implementation backed by Caffeine. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | package io.micronaut.cache.caffeine; 23 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/groovy/io/micronaut/cache/SimpleController.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.http.HttpStatus 4 | import io.micronaut.http.annotation.Controller 5 | import io.micronaut.http.annotation.Get 6 | import io.micronaut.http.annotation.Status 7 | 8 | @Controller 9 | class SimpleController { 10 | 11 | private final CounterService counterService 12 | 13 | SimpleController(CounterService counterService) { 14 | this.counterService = counterService 15 | } 16 | 17 | @Get("/inc") 18 | int increment() { 19 | return counterService.increment("test") 20 | } 21 | 22 | @Get("/get") 23 | int get() { 24 | return counterService.getValue("test") 25 | } 26 | 27 | @Get("/del") 28 | @Status(HttpStatus.FOUND) 29 | void del() { 30 | counterService.reset("test") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/News.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.serde.annotation.Serdeable; 4 | 5 | import java.time.Month; 6 | import java.util.List; 7 | 8 | @Serdeable 9 | public class News { 10 | private Month month; 11 | 12 | private List headlines; 13 | 14 | public News() { 15 | } 16 | 17 | public News(Month month, List headlines) { 18 | this.month = month; 19 | this.headlines = headlines; 20 | } 21 | 22 | public Month getMonth() { 23 | return month; 24 | } 25 | 26 | public void setMonth(Month month) { 27 | this.month = month; 28 | } 29 | 30 | public List getHeadlines() { 31 | return headlines; 32 | } 33 | 34 | public void setHeadlines(List headlines) { 35 | this.headlines = headlines; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test-suite-caffeine-java/src/test/java/io/micronaut/cache/SimpleController.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.http.HttpStatus; 4 | import io.micronaut.http.annotation.Controller; 5 | import io.micronaut.http.annotation.Get; 6 | import io.micronaut.http.annotation.Status; 7 | 8 | @Controller 9 | public class SimpleController { 10 | 11 | private final CounterService counterService; 12 | 13 | public SimpleController(CounterService counterService) { 14 | this.counterService = counterService; 15 | } 16 | 17 | @Get("/inc") 18 | public int increment() { 19 | return counterService.increment("test"); 20 | } 21 | 22 | @Get("/get") 23 | public int get() { 24 | return counterService.getValue("test"); 25 | } 26 | 27 | @Get("/del") 28 | @Status(HttpStatus.FOUND) 29 | public void del() { 30 | counterService.reset("test"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/discovery/client/NativeDiscoveryTest.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.discovery.client; 2 | 3 | import io.micronaut.cache.discovery.CachingCompositeDiscoveryClient; 4 | import io.micronaut.discovery.ServiceInstance; 5 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 6 | import jakarta.inject.Inject; 7 | import org.junit.jupiter.api.Test; 8 | import reactor.core.publisher.Flux; 9 | 10 | import java.util.List; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | @MicronautTest 15 | public class NativeDiscoveryTest { 16 | 17 | @Inject 18 | CachingCompositeDiscoveryClient cachingCompositeDiscoveryClient; 19 | 20 | @Test 21 | void test() { 22 | List instances = Flux.from(cachingCompositeDiscoveryClient.getInstances("test")).blockFirst(); 23 | assertTrue(instances.isEmpty()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/SimpleController.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.http.HttpStatus; 4 | import io.micronaut.http.annotation.Controller; 5 | import io.micronaut.http.annotation.Get; 6 | import io.micronaut.http.annotation.Status; 7 | 8 | @Controller 9 | public class SimpleController { 10 | 11 | private final CounterService counterService; 12 | 13 | public SimpleController(CounterService counterService) { 14 | this.counterService = counterService; 15 | } 16 | 17 | @Get("/inc") 18 | public int increment() { 19 | return counterService.increment("test"); 20 | } 21 | 22 | @Get("/get") 23 | public int get() { 24 | return counterService.getValue("test"); 25 | } 26 | 27 | @Get("/del") 28 | @Status(HttpStatus.FOUND) 29 | public void del() { 30 | counterService.reset("test"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-suite-hazelcast-java/src/test/java/io/micronaut/cache/HazelcastAdditionalSettings.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.hazelcast.HazelcastClientConfiguration; 4 | import io.micronaut.context.event.BeanCreatedEvent; 5 | import io.micronaut.context.event.BeanCreatedEventListener; 6 | import org.jspecify.annotations.NonNull; 7 | import jakarta.inject.Singleton; 8 | 9 | // tag::clazz[] 10 | @Singleton 11 | public class HazelcastAdditionalSettings implements BeanCreatedEventListener { 12 | 13 | @Override 14 | public HazelcastClientConfiguration onCreated(@NonNull BeanCreatedEvent event) { 15 | HazelcastClientConfiguration configuration = event.getBean(); 16 | // Set anything on the configuration 17 | configuration.setClusterName("dev"); 18 | 19 | return configuration; 20 | } 21 | } 22 | // end::clazz[] 23 | -------------------------------------------------------------------------------- /test-suite-hazelcast-java/src/test/java/io/micronaut/cache/SimpleController.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.http.HttpStatus; 4 | import io.micronaut.http.annotation.Controller; 5 | import io.micronaut.http.annotation.Get; 6 | import io.micronaut.http.annotation.Status; 7 | 8 | @Controller 9 | public class SimpleController { 10 | 11 | private final CounterService counterService; 12 | 13 | public SimpleController(CounterService counterService) { 14 | this.counterService = counterService; 15 | } 16 | 17 | @Get("/inc") 18 | public int increment() { 19 | return counterService.increment("test"); 20 | } 21 | 22 | @Get("/get") 23 | public int get() { 24 | return counterService.getValue("test"); 25 | } 26 | 27 | @Get("/del") 28 | @Status(HttpStatus.FOUND) 29 | public void del() { 30 | counterService.reset("test"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-suite-caffeine-java/src/test/java/io/micronaut/cache/ConditionalService.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.annotation.CacheConfig; 4 | import io.micronaut.cache.annotation.Cacheable; 5 | import jakarta.inject.Singleton; 6 | 7 | @Singleton 8 | @CacheConfig(cacheNames = {"conditional"}) 9 | public class ConditionalService { 10 | 11 | DocRepo repository = new DocRepo(); 12 | 13 | // tag::conditional[] 14 | public record Id(Integer value) { 15 | } 16 | 17 | @Cacheable(condition = "#{id.value > 5}") 18 | public String get(Id id) { 19 | return repository.get(id); 20 | } 21 | // end::conditional[] 22 | 23 | // here to make the docs look nice when we extract the function above 24 | private static class DocRepo { 25 | 26 | String get(Id id) { 27 | return "test " + id.value + " " + System.currentTimeMillis(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cache-tck/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.micronaut.build.utils.DefaultVersions 2 | 3 | plugins { 4 | id("groovy") 5 | id("io.micronaut.build.internal.cache-module") 6 | } 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | compileOnly(mn.micronaut.inject.groovy) 14 | 15 | implementation(projects.micronautCacheCore) 16 | implementation(mn.reactor) 17 | 18 | // The following dependencies are implementation and runtimeOnly 19 | // because it's a TCK, so it's not test dependencies for this module 20 | // but test dependencies for modules that would depend on the TCK! 21 | 22 | implementation("org.spockframework:spock-core") { 23 | version { 24 | require(DefaultVersions.SPOCK_VERSION) 25 | } 26 | } 27 | runtimeOnly("org.objenesis:objenesis") { 28 | version { 29 | require(DefaultVersions.OBJENESIS_VERSION) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.cache-test-suite") 3 | id("groovy") 4 | } 5 | 6 | dependencies { 7 | testImplementation(projects.micronautCacheCaffeine) 8 | 9 | testImplementation(mn.micronaut.inject.groovy) 10 | testImplementation(mn.micronaut.http.client) 11 | testImplementation(mn.micronaut.http.server.netty) 12 | testImplementation(mnSerde.micronaut.serde.jackson) 13 | testImplementation(mnTest.micronaut.test.spock) 14 | 15 | testRuntimeOnly(mnTest.junit.jupiter.engine) 16 | testRuntimeOnly(mnLogging.logback.classic) 17 | testRuntimeOnly(mnTest.junit.platform.suite) 18 | } 19 | 20 | tasks.withType().configureEach { 21 | useJUnitPlatform() 22 | } 23 | //TODO remove once Micronaut Test ships Spock version compatible with Groovy 5 24 | configurations.all { 25 | resolutionStrategy { 26 | force("org.spockframework:spock-core:2.4-M7-groovy-5.0") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .DS_Store 3 | target/ 4 | .gradle/ 5 | .idea/ 6 | build/ 7 | !build-logic/src/main/java/io/micronaut/build 8 | !build-logic/src/main/kotlin/io/micronaut/build 9 | classes/ 10 | out/ 11 | *.db 12 | *.log 13 | *.iml 14 | .classpath 15 | .factorypath 16 | bin/ 17 | .settings/ 18 | .project 19 | */test/ 20 | */META-INF/ 21 | *.ipr 22 | *.iws 23 | .kotlintest 24 | */.kotlintest/ 25 | 26 | # ignore resources, are downloaded via a gradle task from micronaut_docs 27 | src/main/docs/resources/css/highlight/*.css 28 | src/main/docs/resources/css/highlight/*.png 29 | src/main/docs/resources/css/highlight/*.jpg 30 | src/main/docs/resources/css/*.css 31 | src/main/docs/resources/js/*.js 32 | src/main/docs/resources/style/*.html 33 | src/main/docs/resources/img/micronaut-logo-white.svg 34 | 35 | # Ignore files generated by test-resources 36 | **/.micronaut/test-resources/ 37 | 38 | # Ignore gradle.properties generated by micronaut-build 39 | /buildSrc/gradle.properties 40 | .kotlin 41 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/java/io/micronaut/cache/ConditionalService.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.annotation.CacheConfig; 4 | import io.micronaut.cache.annotation.Cacheable; 5 | import io.micronaut.context.annotation.Requires; 6 | import jakarta.inject.Singleton; 7 | 8 | @Singleton 9 | @CacheConfig(cacheNames = {"conditional"}) 10 | @Requires(property = "spec.name", value = "ConditionalCacheSpec") 11 | public class ConditionalService { 12 | 13 | DocRepo repository = new DocRepo(); 14 | 15 | // tag::conditional[] 16 | @Cacheable(condition = "#{id > 5}") 17 | public String get(Integer id) { 18 | return repository.get(id); 19 | } 20 | // end::conditional[] 21 | 22 | // here to make the docs look nice when we extract the function above 23 | private static class DocRepo { 24 | 25 | String get(Integer id) { 26 | return "test " + id + " " + System.currentTimeMillis(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/groovy/io/micronaut/cache/ConditionalCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.context.annotation.Property 4 | import io.micronaut.test.extensions.spock.annotation.MicronautTest 5 | import jakarta.inject.Inject 6 | import spock.lang.Specification 7 | 8 | @MicronautTest 9 | @Property(name = "spec.name", value = "ConditionalCacheSpec") 10 | class ConditionalCacheSpec extends Specification { 11 | 12 | @Inject 13 | ConditionalService service 14 | 15 | void "test conditional cache"() { 16 | when: 17 | List results = (1..10).collect { service.get(it) } 18 | sleep(10) 19 | List secondResults = (1..10).collect { service.get(it) } 20 | 21 | then: "condition results in the last 5 results being cached" 22 | results.drop(5) == secondResults.drop(5) 23 | 24 | and: "the first 5 results are not cached" 25 | results.take(5).every {!secondResults.contains(it) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/groovy/io/micronaut/cache/ConditionalService.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import groovy.transform.Canonical 4 | import groovy.transform.Immutable 5 | import io.micronaut.cache.annotation.CacheConfig 6 | import io.micronaut.cache.annotation.Cacheable 7 | import jakarta.inject.Singleton 8 | 9 | @Singleton 10 | @CacheConfig(cacheNames = ["conditional"]) 11 | class ConditionalService { 12 | 13 | DocRepo repository = new DocRepo() 14 | 15 | // tag::conditional[] 16 | @Immutable 17 | static class Id { 18 | int value 19 | } 20 | 21 | @Cacheable(condition = "#{id.value > 5}") 22 | String get(Id id) { 23 | return repository.get(id) 24 | } 25 | // end::conditional[] 26 | 27 | // here to make the docs look nice when we extract the function above 28 | private static class DocRepo { 29 | 30 | String get(Id id) { 31 | return "test $id.value ${System.currentTimeMillis()}" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test-suite-hazelcast-java/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | testAnnotationProcessor(mn.micronaut.inject.java) 11 | 12 | testImplementation(projects.micronautCacheHazelcast) 13 | 14 | testImplementation(mn.micronaut.http.client) 15 | testImplementation(mn.micronaut.http.server.netty) 16 | testImplementation(mnSerde.micronaut.serde.jackson) 17 | testImplementation(mnTest.micronaut.test.junit5) 18 | testImplementation(platform(mnTestResources.boms.testcontainers)) 19 | testImplementation(libs.testcontainers.junit) 20 | testImplementation(mn.jspecify) 21 | 22 | testRuntimeOnly(mnTest.junit.jupiter.engine) 23 | testRuntimeOnly(mnLogging.logback.classic) 24 | testRuntimeOnly(mnTest.junit.platform.suite) 25 | } 26 | 27 | tasks.withType().configureEach { 28 | useJUnitPlatform() 29 | systemProperty("hazelcastVersion", libs.versions.managed.hazelcast.get()) 30 | } 31 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/jcache/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Contains integration with JCache managers. 18 | * 19 | * @author graemerocher 20 | * @since 1.0 21 | */ 22 | @Requires(classes = CacheManager.class) 23 | package io.micronaut.cache.jcache; 24 | 25 | import io.micronaut.context.annotation.Requires; 26 | 27 | import javax.cache.CacheManager; 28 | -------------------------------------------------------------------------------- /test-suite-hazelcast-groovy/src/test/groovy/io/micronaut/cache/CounterService.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.annotation.CacheConfig 4 | import io.micronaut.cache.annotation.CacheInvalidate 5 | import io.micronaut.cache.annotation.CachePut 6 | import io.micronaut.cache.annotation.Cacheable 7 | import jakarta.inject.Singleton 8 | 9 | @Singleton 10 | @CacheConfig(cacheNames = ["counter"]) 11 | class CounterService { 12 | 13 | Map counters = [:] 14 | 15 | @CachePut 16 | int increment(String name) { 17 | int value = counters.computeIfAbsent(name, s -> 0) 18 | counters.put(name, ++value) 19 | value 20 | } 21 | 22 | @Cacheable 23 | int getValue(String name) { 24 | counters.computeIfAbsent(name, s -> 0) 25 | } 26 | 27 | 28 | @CacheInvalidate() 29 | void reset(String name) { 30 | counters.remove(name) 31 | } 32 | 33 | @CacheInvalidate 34 | void set(String name, int val) { 35 | counters.put(name, val) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test-suite-hazelcast-kotlin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.cache-test-suite-kotlin") 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | kaptTest(mn.micronaut.inject.java) 11 | 12 | testImplementation(projects.micronautCacheHazelcast) 13 | 14 | testImplementation(mn.micronaut.http.client) 15 | testImplementation(mn.micronaut.http.server.netty) 16 | testImplementation(mn.kotlinx.coroutines.core) 17 | testImplementation(mnSerde.micronaut.serde.jackson) 18 | testImplementation(mnTest.micronaut.test.junit5) 19 | testImplementation(platform(mnTestResources.boms.testcontainers)) 20 | testImplementation(libs.testcontainers.junit) 21 | 22 | testRuntimeOnly(mnTest.junit.jupiter.engine) 23 | testRuntimeOnly(mnLogging.logback.classic) 24 | testRuntimeOnly(mnTest.junit.platform.suite) 25 | } 26 | 27 | tasks.withType().configureEach { 28 | useJUnitPlatform() 29 | systemProperty("hazelcastVersion", libs.versions.managed.hazelcast.get()) 30 | } 31 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/discovery/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Caches related to the discovery client. 18 | * 19 | * @author James Kleeh 20 | * @since 2.0.0 21 | */ 22 | @Configuration 23 | @Requires(configuration = "io.micronaut.discovery.client") 24 | package io.micronaut.cache.discovery; 25 | 26 | import io.micronaut.context.annotation.Configuration; 27 | import io.micronaut.context.annotation.Requires; 28 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/groovy/io/micronaut/cache/CounterService.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.annotation.CacheConfig 4 | import io.micronaut.cache.annotation.CacheInvalidate 5 | import io.micronaut.cache.annotation.CachePut 6 | import io.micronaut.cache.annotation.Cacheable 7 | import jakarta.inject.Singleton 8 | 9 | @Singleton 10 | @CacheConfig(cacheNames = ["counter"]) 11 | public class CounterService { 12 | 13 | Map counters = [:] 14 | 15 | @CachePut 16 | int increment(String name) { 17 | int value = counters.computeIfAbsent(name, s -> 0) 18 | counters.put(name, ++value) 19 | return value 20 | } 21 | 22 | @Cacheable 23 | int getValue(String name) { 24 | return counters.computeIfAbsent(name, s -> 0) 25 | } 26 | 27 | 28 | @CacheInvalidate() 29 | void reset(String name) { 30 | counters.remove(name) 31 | } 32 | 33 | @CacheInvalidate 34 | void set(String name, int val) { 35 | counters.put(name, val) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cache-management/src/main/java/io/micronaut/cache/management/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Caches management endpoint. 18 | * 19 | * @author Marcel Overdijk 20 | * @since 1.1.0 21 | */ 22 | @Configuration 23 | @Requires(configuration = "io.micronaut.management.endpoint") 24 | package io.micronaut.cache.management; 25 | 26 | import io.micronaut.context.annotation.Configuration; 27 | import io.micronaut.context.annotation.Requires; 28 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/kotlin/io/micronaut/cache/CounterService.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.annotation.CacheConfig 4 | import io.micronaut.cache.annotation.CacheInvalidate 5 | import io.micronaut.cache.annotation.CachePut 6 | import io.micronaut.cache.annotation.Cacheable 7 | import jakarta.inject.Singleton 8 | 9 | @Singleton 10 | @CacheConfig(cacheNames = ["counter"]) 11 | open class CounterService { 12 | 13 | private val counters: MutableMap = mutableMapOf() 14 | 15 | @CachePut 16 | open fun increment(name: String): Int { 17 | var value = counters.computeIfAbsent(name) { s: String? -> 0 } 18 | counters[name] = ++value 19 | return value 20 | } 21 | 22 | @Cacheable 23 | open fun getValue(name: String) = counters.computeIfAbsent(name) { s: String? -> 0 } 24 | 25 | @CacheInvalidate 26 | open fun reset(name: String) = counters.remove(name) 27 | 28 | @CacheInvalidate 29 | open operator fun set(name: String, value: Int) { 30 | counters[name] = value 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-suite-hazelcast-kotlin/src/test/kotlin/io/micronaut/cache/CounterService.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.annotation.CacheConfig 4 | import io.micronaut.cache.annotation.CacheInvalidate 5 | import io.micronaut.cache.annotation.CachePut 6 | import io.micronaut.cache.annotation.Cacheable 7 | import jakarta.inject.Singleton 8 | 9 | @Singleton 10 | @CacheConfig(cacheNames = ["counter"]) 11 | open class CounterService { 12 | 13 | private val counters: MutableMap = mutableMapOf() 14 | 15 | @CachePut 16 | open fun increment(name: String): Int { 17 | var value = counters.computeIfAbsent(name) { s: String? -> 0 } 18 | counters[name] = ++value 19 | return value 20 | } 21 | 22 | @Cacheable 23 | open fun getValue(name: String) = counters.computeIfAbsent(name) { s: String? -> 0 } 24 | 25 | @CacheInvalidate 26 | open fun reset(name: String) = counters.remove(name) 27 | 28 | @CacheInvalidate 29 | open operator fun set(name: String, value: Int) { 30 | counters[name] = value 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/DefaultCacheErrorHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import io.micronaut.context.annotation.Primary; 19 | import jakarta.inject.Singleton; 20 | 21 | /** 22 | * Default implementation of {@link CacheErrorHandler}. 23 | * 24 | * @author Graeme Rocher 25 | * @since 1.0 26 | */ 27 | @Singleton 28 | @Primary 29 | public class DefaultCacheErrorHandler implements CacheErrorHandler { 30 | } 31 | -------------------------------------------------------------------------------- /cache-ehcache/src/main/java/io/micronaut/cache/ehcache/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Classes related to the integration of Ehcache caches into Micronaut's 18 | * cache manager. 19 | */ 20 | @Configuration 21 | @Requires(property = "ehcache.enabled", notEquals = StringUtils.FALSE) 22 | package io.micronaut.cache.ehcache; 23 | 24 | import io.micronaut.context.annotation.Configuration; 25 | import io.micronaut.context.annotation.Requires; 26 | import io.micronaut.core.util.StringUtils; 27 | -------------------------------------------------------------------------------- /cache-hazelcast/src/main/java/io/micronaut/cache/hazelcast/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Classes related to the integration of Hazelcast caches into Micronaut's 18 | * cache manager. 19 | */ 20 | @Configuration 21 | @Requires(property = "hazelcast.enabled", notEquals = StringUtils.FALSE) 22 | package io.micronaut.cache.hazelcast; 23 | 24 | import io.micronaut.context.annotation.Configuration; 25 | import io.micronaut.context.annotation.Requires; 26 | import io.micronaut.core.util.StringUtils; 27 | -------------------------------------------------------------------------------- /cache-infinispan/src/main/java/io/micronaut/cache/infinispan/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /** 17 | * Infinispan support. 18 | * 19 | * @author Álvaro Sánchez-Mariscal 20 | * @since 1.0.0 21 | */ 22 | @Configuration 23 | @Requires(property = "infinispan.enabled", notEquals = StringUtils.FALSE) 24 | package io.micronaut.cache.infinispan; 25 | 26 | import io.micronaut.context.annotation.Configuration; 27 | import io.micronaut.context.annotation.Requires; 28 | import io.micronaut.core.util.StringUtils; 29 | -------------------------------------------------------------------------------- /test-suite-hazelcast-groovy/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.cache-test-suite") 3 | id("groovy") 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | testImplementation(projects.micronautCacheHazelcast) 12 | 13 | testImplementation(mn.micronaut.inject.groovy) 14 | testImplementation(mn.micronaut.http.client) 15 | testImplementation(mn.micronaut.http.server.netty) 16 | testImplementation(mnSerde.micronaut.serde.jackson) 17 | testImplementation(mnTest.micronaut.test.spock) 18 | testImplementation(mnTestResources.testcontainers.core) 19 | 20 | testRuntimeOnly(mnTest.junit.jupiter.engine) 21 | testRuntimeOnly(mnLogging.logback.classic) 22 | testRuntimeOnly(mnTest.junit.platform.suite) 23 | } 24 | 25 | tasks.withType().configureEach { 26 | useJUnitPlatform() 27 | systemProperty("hazelcastVersion", libs.versions.managed.hazelcast.get()) 28 | } 29 | //TODO remove once Micronaut Test ships Spock version compatible with Groovy 5 30 | configurations.all { 31 | resolutionStrategy { 32 | force("org.spockframework:spock-core:2.4-M7-groovy-5.0") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for reporting an issue, please review the task list below before submitting the 2 | issue. Your issue report will be closed if the issue is incomplete and the below tasks not completed. 3 | 4 | NOTE: If you are unsure about something and the issue is more of a question a better place to ask questions is on Stack Overflow (https://stackoverflow.com/tags/micronaut) or Gitter (https://gitter.im/micronautfw/). DO NOT use the issue tracker to ask questions. 5 | 6 | ### Task List 7 | 8 | - [ ] Steps to reproduce provided 9 | - [ ] Stacktrace (if present) provided 10 | - [ ] Example that reproduces the problem uploaded to Github 11 | - [ ] Full description of the issue provided (see below) 12 | 13 | ### Steps to Reproduce 14 | 15 | 1. TODO 16 | 2. TODO 17 | 3. TODO 18 | 19 | ### Expected Behaviour 20 | 21 | Tell us what should happen 22 | 23 | ### Actual Behaviour 24 | 25 | Tell us what happens instead 26 | 27 | ### Environment Information 28 | 29 | - **Operating System**: TODO 30 | - **Micronaut Version:** TODO 31 | - **JDK Version:** TODO 32 | 33 | ### Example Application 34 | 35 | - TODO: link to github repository with example that reproduces the issue 36 | 37 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | plugins { 9 | id 'io.micronaut.build.shared.settings' version '8.0.0-M12' 10 | } 11 | 12 | rootProject.name = 'cache-parent' 13 | 14 | include 'cache-bom' 15 | include 'cache-caffeine' 16 | include 'cache-core' 17 | include 'cache-ehcache' 18 | include 'cache-hazelcast' 19 | include 'cache-infinispan' 20 | include 'cache-management' 21 | include 'cache-noop' 22 | include 'cache-tck' 23 | 24 | include 'test-suite-caffeine-groovy' 25 | include 'test-suite-caffeine-java' 26 | include 'test-suite-caffeine-kotlin' 27 | 28 | include 'test-suite-hazelcast-groovy' 29 | include 'test-suite-hazelcast-java' 30 | include 'test-suite-hazelcast-kotlin' 31 | 32 | include 'test-suite-caffeine-native' 33 | 34 | enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS' 35 | 36 | micronautBuild { 37 | useStandardizedProjectNames = true 38 | importMicronautCatalog() 39 | importMicronautCatalog("micronaut-micrometer") 40 | importMicronautCatalog("micronaut-serde") 41 | importMicronautCatalog("micronaut-test-resources") 42 | importMicronautCatalog("micronaut-validation") 43 | } 44 | -------------------------------------------------------------------------------- /src/main/docs/guide/annotations.adoc: -------------------------------------------------------------------------------- 1 | The following cache annotations are supported: 2 | 3 | - link:{api}/io/micronaut/cache/annotation/Cacheable.html[@Cacheable] - Indicates a method is cacheable within the given cache name 4 | - link:{api}/io/micronaut/cache/annotation/CachePut.html[@CachePut] - Indicates that the return value of a method invocation should be cached. Unlike `@Cacheable` the original operation is never skipped. 5 | - link:{api}/io/micronaut/cache/annotation/CacheInvalidate.html[@CacheInvalidate] - Indicates the invocation of a method should cause the invalidation of one or many caches. 6 | 7 | By using one of the annotations the api:cache.interceptor.CacheInterceptor[] is activated which in the case of `@Cacheable` will cache the return result of the method. 8 | 9 | If the return type of the method is a non-blocking type (either link:{jdkapi}/java.base/java/util/concurrent/CompletableFuture.html[CompletableFuture] or an instance of rs:Publisher[] the emitted result will be cached. 10 | 11 | In addition if the underlying Cache implementation supports non-blocking cache operations then cache values will be read from the cache without blocking, resulting in the ability to implement completely non-blocking cache operations. 12 | -------------------------------------------------------------------------------- /test-suite-caffeine-java/src/test/java/io/micronaut/cache/CounterService.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.annotation.CacheConfig; 4 | import io.micronaut.cache.annotation.CacheInvalidate; 5 | import io.micronaut.cache.annotation.CachePut; 6 | import io.micronaut.cache.annotation.Cacheable; 7 | import jakarta.inject.Singleton; 8 | 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author Graeme Rocher 14 | * @since 1.0 15 | */ 16 | @Singleton 17 | @CacheConfig(cacheNames = {"counter"}) 18 | public class CounterService { 19 | 20 | Map counters = new LinkedHashMap<>(); 21 | 22 | @CachePut 23 | public int increment(String name) { 24 | int value = counters.computeIfAbsent(name, s -> 0); 25 | counters.put(name, ++value); 26 | return value; 27 | } 28 | 29 | @Cacheable 30 | public int getValue(String name) { 31 | return counters.computeIfAbsent(name, s -> 0); 32 | } 33 | 34 | 35 | @CacheInvalidate() 36 | public void reset(String name) { 37 | counters.remove(name); 38 | } 39 | 40 | @CacheInvalidate 41 | public void set(String name, int val) { 42 | counters.put(name, val); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test-suite-hazelcast-java/src/test/java/io/micronaut/cache/CounterService.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.annotation.CacheConfig; 4 | import io.micronaut.cache.annotation.CacheInvalidate; 5 | import io.micronaut.cache.annotation.CachePut; 6 | import io.micronaut.cache.annotation.Cacheable; 7 | import jakarta.inject.Singleton; 8 | 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author Graeme Rocher 14 | * @since 1.0 15 | */ 16 | @Singleton 17 | @CacheConfig(cacheNames = {"counter"}) 18 | public class CounterService { 19 | 20 | Map counters = new LinkedHashMap<>(); 21 | 22 | @CachePut 23 | public int increment(String name) { 24 | int value = counters.computeIfAbsent(name, s -> 0); 25 | counters.put(name, ++value); 26 | return value; 27 | } 28 | 29 | @Cacheable 30 | public int getValue(String name) { 31 | return counters.computeIfAbsent(name, s -> 0); 32 | } 33 | 34 | 35 | @CacheInvalidate() 36 | public void reset(String name) { 37 | counters.remove(name); 38 | } 39 | 40 | @CacheInvalidate 41 | public void set(String name, int val) { 42 | counters.put(name, val); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cache-ehcache/src/test/groovy/io/micronaut/cache/ehcache/EhcacheSyncCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache 17 | 18 | import io.micronaut.cache.tck.AbstractSyncCacheSpec 19 | import io.micronaut.context.ApplicationContext 20 | 21 | class EhcacheSyncCacheSpec extends AbstractSyncCacheSpec { 22 | 23 | @Override 24 | ApplicationContext createApplicationContext() { 25 | return ApplicationContext.run( 26 | "ehcache.caches.counter.enabled": true, 27 | "ehcache.caches.counter2.enabled": true, 28 | "ehcache.caches.test.enabled": true 29 | ) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/CacheInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | import io.micronaut.core.async.annotation.SingleResult; 20 | import io.micronaut.core.naming.Named; 21 | 22 | import java.util.Map; 23 | 24 | /** 25 | * Provides information about the state of the cache. 26 | * 27 | * @author graemerocher 28 | * @since 1.1 29 | */ 30 | public interface CacheInfo extends Named { 31 | 32 | /** 33 | * A publisher that emits a single result containing the cache data. 34 | * @return Cache data 35 | */ 36 | @NonNull 37 | @SingleResult 38 | Map get(); 39 | } 40 | -------------------------------------------------------------------------------- /cache-ehcache/src/test/groovy/io/micronaut/cache/ehcache/EhcacheAsyncCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache 17 | 18 | import io.micronaut.cache.AsyncCache 19 | import io.micronaut.cache.tck.AbstractAsyncCacheSpec 20 | import io.micronaut.context.ApplicationContext 21 | 22 | class EhcacheAsyncCacheSpec extends AbstractAsyncCacheSpec { 23 | 24 | @Override 25 | ApplicationContext createApplicationContext() { 26 | return ApplicationContext.run( 27 | "ehcache.caches.counter.enabled": true, 28 | "ehcache.caches.counter2.enabled": true, 29 | "ehcache.caches.test.enabled": true 30 | ) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/NewsControllerTest.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.http.HttpRequest; 4 | import io.micronaut.http.client.HttpClient; 5 | import io.micronaut.http.client.annotation.Client; 6 | import io.micronaut.http.uri.UriBuilder; 7 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 8 | import jakarta.inject.Inject; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.Timeout; 11 | 12 | import java.time.Month; 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | @MicronautTest 18 | class NewsControllerTest { 19 | 20 | @Inject 21 | @Client("/") 22 | HttpClient client; 23 | 24 | @Timeout(4) 25 | @Test 26 | void fetchingOctoberHeadlinesUsesCache() { 27 | HttpRequest request = HttpRequest.GET(UriBuilder.of("/").path(Month.OCTOBER.toString()).build()); 28 | 29 | News news = client.toBlocking().retrieve(request, News.class); 30 | String expected = "Micronaut AOP: Awesome flexibility without the complexity"; 31 | assertEquals(List.of(expected), news.getHeadlines()); 32 | 33 | news = client.toBlocking().retrieve(request, News.class); 34 | assertEquals(List.of(expected), news.getHeadlines()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | projectVersion=6.0.0-SNAPSHOT 2 | projectGroup=io.micronaut.cache 3 | 4 | title=Micronaut Cache 5 | projectDesc=Cache support for Micronaut 6 | projectUrl=https://micronaut.io 7 | githubSlug=micronaut-projects/micronaut-cache 8 | developers=Alvaro Sanchez-Mariscal 9 | 10 | jdkapi=https://docs.oracle.com/en/java/javase/17/docs/api 11 | 12 | org.gradle.caching=true 13 | org.gradle.parallel=true 14 | org.gradle.jvmargs=-Xmx1g 15 | 16 | # No matter which Java toolchain we use, the Kotlin Daemon is always invoked by the current JDK. 17 | # Therefor to fix Kapt errors when running tests under Java 21, we need to open up some modules for the Kotlin Daemon. 18 | kotlin.daemon.jvmargs=--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED\ 19 | --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ 20 | --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \ 21 | --add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \ 22 | --add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \ 23 | --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \ 24 | --add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ 25 | --add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \ 26 | --add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ 27 | --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 28 | -------------------------------------------------------------------------------- /cache-ehcache/src/main/java/io/micronaut/cache/ehcache/configuration/AbstractResourcePoolConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache.configuration; 17 | 18 | import io.micronaut.core.convert.format.ReadableBytes; 19 | 20 | /** 21 | * Common resource pools configuration properties. 22 | */ 23 | public abstract class AbstractResourcePoolConfiguration { 24 | private Long maxSize; 25 | 26 | /** 27 | * @return The maximum size of the cache, in bytes 28 | */ 29 | public Long getMaxSize() { 30 | return maxSize; 31 | } 32 | 33 | /** 34 | * @param maxSize The maximum size of the cache, in bytes 35 | */ 36 | public void setMaxSize(@ReadableBytes Long maxSize) { 37 | this.maxSize = maxSize; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/interceptor/CacheKeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.interceptor; 17 | 18 | import io.micronaut.core.annotation.AnnotationMetadata; 19 | 20 | 21 | /** 22 | *

An interface for generating keys used by {@link io.micronaut.cache.annotation.Cacheable}.

23 | * 24 | * @author Graeme Rocher 25 | * @since 1.0 26 | */ 27 | public interface CacheKeyGenerator { 28 | 29 | /** 30 | * Generate a key for the given annotated element and parameters. 31 | * 32 | * @param annotationMetadata The annotated metadata 33 | * @param params The parameters 34 | * @return The generated key. Never null. 35 | */ 36 | Object generateKey(AnnotationMetadata annotationMetadata, Object... params); 37 | } 38 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/annotation/CacheAnnotation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2021 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.annotation; 17 | 18 | import io.micronaut.aop.Around; 19 | import io.micronaut.core.annotation.Internal; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.ElementType; 23 | import java.lang.annotation.Inherited; 24 | import java.lang.annotation.Retention; 25 | import java.lang.annotation.RetentionPolicy; 26 | import java.lang.annotation.Target; 27 | 28 | /** 29 | * Meta annotation to mark cache operations. 30 | * 31 | * @author Álvaro Sánchez-Mariscal 32 | * @since 2.3.0 33 | */ 34 | @Target({ElementType.ANNOTATION_TYPE}) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Documented 37 | @Around 38 | @Internal 39 | @Inherited 40 | public @interface CacheAnnotation { 41 | } 42 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import org.reactivestreams.Publisher; 19 | import reactor.core.publisher.Flux; 20 | 21 | /** 22 | *

Base cache interface implemented by both {@link SyncCache} and {@link AsyncCache}.

23 | * 24 | * @param The native cache implementation 25 | * 26 | * @author Graeme Rocher 27 | * @since 1.0 28 | */ 29 | public interface Cache { 30 | 31 | /** 32 | * @return The name of the cache 33 | */ 34 | String getName(); 35 | 36 | /** 37 | * @return The native cache implementation 38 | */ 39 | C getNativeCache(); 40 | 41 | /** 42 | * @return The cache information. 43 | */ 44 | default Publisher getCacheInfo() { 45 | return Flux.empty(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/annotation/PutOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.annotation; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Inherited; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** 26 | * Allows for repeated annotations of the type {@link CachePut}. 27 | * 28 | * @author Graeme Rocher 29 | * @since 1.0 30 | */ 31 | @Target({ElementType.METHOD, ElementType.TYPE}) 32 | @Retention(RetentionPolicy.RUNTIME) 33 | @Inherited 34 | @Documented 35 | @CacheAnnotation 36 | public @interface PutOperations { 37 | 38 | /** 39 | * @return The {@link CachePut} instances for the method 40 | */ 41 | CachePut[] value(); 42 | } 43 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/DynamicCacheManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | 20 | /** 21 | *

A contract for a cache manager that does not have pre-defined caches.

22 | * 23 | * @param The native cache implementation 24 | * 25 | * @author James Kleeh 26 | * @since 1.3.0 27 | */ 28 | public interface DynamicCacheManager { 29 | 30 | /** 31 | * Retrieve a cache for the given name. If the cache does not previously exist, a new one will be created. 32 | * The cache instance should not be cached internally because the cache manager will maintain the instance 33 | * for future requests. 34 | * 35 | * @param name The name of the cache 36 | * @return The {@link SyncCache} instance 37 | */ 38 | @NonNull 39 | SyncCache getCache(String name); 40 | } 41 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/annotation/InvalidateOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.annotation; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Inherited; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | /** 26 | * Allows for repeated annotations of the type {@link CacheInvalidate}. 27 | * 28 | * @author Graeme Rocher 29 | * @since 1.0 30 | */ 31 | @Target({ElementType.METHOD, ElementType.TYPE}) 32 | @Retention(RetentionPolicy.RUNTIME) 33 | @Inherited 34 | @Documented 35 | @CacheAnnotation 36 | public @interface InvalidateOperations { 37 | 38 | /** 39 | * @return The {@link CacheInvalidate} instances for the method 40 | */ 41 | CacheInvalidate[] value(); 42 | } 43 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/CacheManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import org.jspecify.annotations.NonNull; 19 | 20 | import java.util.Set; 21 | 22 | /** 23 | *

Simple CacheManager interface for managing caches.

24 | * 25 | * @param The native cache implementation 26 | * 27 | * @author Graeme Rocher 28 | * @since 1.0 29 | */ 30 | public interface CacheManager { 31 | 32 | /** 33 | * @return The names of the active caches 34 | */ 35 | @NonNull 36 | Set getCacheNames(); 37 | 38 | /** 39 | * Retrieve a cache for the given name. 40 | * 41 | * @param name The name of the cache 42 | * @return The {@link SyncCache} instance 43 | * @throws io.micronaut.context.exceptions.ConfigurationException If no cache is found for the given name 44 | */ 45 | @NonNull SyncCache getCache(String name); 46 | } 47 | -------------------------------------------------------------------------------- /cache-hazelcast/src/main/java/io/micronaut/cache/hazelcast/converters/MapToConcurrentMapConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.hazelcast.converters; 17 | 18 | import io.micronaut.core.convert.ConversionContext; 19 | import io.micronaut.core.convert.TypeConverter; 20 | import jakarta.inject.Singleton; 21 | 22 | import java.util.Map; 23 | import java.util.Optional; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | import java.util.concurrent.ConcurrentMap; 26 | 27 | /** 28 | * Converts a map to a concurrent map. 29 | * 30 | * @author James Kleeh 31 | * @since 1.0.0 32 | */ 33 | @Singleton 34 | public class MapToConcurrentMapConverter implements TypeConverter { 35 | 36 | @Override 37 | public Optional convert(Map object, Class targetType, ConversionContext context) { 38 | return Optional.of(new ConcurrentHashMap(object)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing a new cache implementation 2 | 3 | How a cache implementation should be created depends on whether the caches can/should be declared ahead of time. For caches with predefined cache names, `SyncCache` beans should be created which will be ingested by the default cache manager. For caches that cannot or should not be defined ahead of time, a `DynamicCacheManager` bean should be registered to retrieve the cache. 4 | 5 | Any other beans created are not used by Micronaut. The only beans referenced are `SyncCache` and `DynamicCacheManager`. 6 | 7 | ## Synchronous cache implementations 8 | 9 | For caches that using blocking IO, the `getExecutorService()` method should be overridden for each `SyncCache` to allow the offloading of the operations to a thread pool. Unless there is a reason to do otherwise, the `TaskManagers.IO` named thread pool should be used. 10 | 11 | ## Asynchronous cache implementations 12 | 13 | If the cache provider has an asynchronous API, the `async()` method of `SyncCache` should be overridden to supply an `AsyncCache` that uses the native asynchronous API. See the Hazelcast implementation for an example. 14 | 15 | ## Gradle build and setup 16 | 17 | To ensure consistency across implementations, copy the build of an existing implementation and alter the dependencies as needed. 18 | 19 | ## Documentation 20 | 21 | All cache implementations should be documented by modifying the `src/main/docs/guide/toc.yml` to include your documentation. Documentation should describe how to install and configure the implementation. 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /cache-management/src/test/groovy/io/micronaut/cache/management/CachesEndpointConfigurationSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.management 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import spock.lang.Specification 20 | 21 | /** 22 | * @author Marcel Overdijk 23 | * @since 1.1 24 | */ 25 | class CachesEndpointConfigurationSpec extends Specification { 26 | 27 | void "test caches endpoint enabled"() { 28 | given: 29 | ApplicationContext context = ApplicationContext.run() 30 | 31 | expect: 32 | !context.containsBean(CachesEndpoint) 33 | 34 | cleanup: 35 | context.close() 36 | } 37 | 38 | void "test caches endpoint can be disabled"() { 39 | given: 40 | ApplicationContext context = ApplicationContext.run(["endpoints.caches.enabled": true]) 41 | 42 | expect: 43 | context.containsBean(CachesEndpoint) 44 | 45 | cleanup: 46 | context.close() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cache-tck/src/main/groovy/io/micronaut/cache/tck/PublisherService.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.tck 17 | 18 | import io.micronaut.cache.annotation.CacheConfig 19 | import io.micronaut.cache.annotation.Cacheable 20 | import io.micronaut.core.async.annotation.SingleResult 21 | import jakarta.inject.Singleton 22 | import org.reactivestreams.Publisher 23 | import reactor.core.publisher.Flux 24 | import reactor.core.publisher.Mono 25 | 26 | import java.util.concurrent.atomic.AtomicInteger 27 | 28 | @Singleton 29 | @CacheConfig('counter') 30 | class PublisherService { 31 | 32 | AtomicInteger callCount = new AtomicInteger() 33 | 34 | @Cacheable 35 | @SingleResult 36 | Publisher fluxValue(String name) { 37 | callCount.incrementAndGet() 38 | return Flux.just(0) 39 | } 40 | 41 | @Cacheable 42 | @SingleResult 43 | Publisher monoValue(String name) { 44 | callCount.incrementAndGet() 45 | return Mono.just(0) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /test-suite-caffeine-native/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.micronaut.build.internal.cache-test-suite") 3 | id 'org.graalvm.buildtools.native' 4 | } 5 | 6 | micronautBuild { 7 | enableBom = false 8 | enableProcessing = false 9 | } 10 | 11 | dependencies { 12 | testAnnotationProcessor(mn.micronaut.inject.java) 13 | testAnnotationProcessor(mnSerde.micronaut.serde.processor) 14 | testImplementation(mnSerde.micronaut.serde.api) 15 | testImplementation(projects.micronautCacheCaffeine) 16 | testImplementation(mn.micronaut.http.server.netty) 17 | testImplementation(mn.micronaut.http.client) 18 | testImplementation(mn.reactor) 19 | testImplementation(mnTest.micronaut.test.junit5) 20 | testRuntimeOnly(mnSerde.micronaut.serde.jackson) 21 | testRuntimeOnly(mnTest.junit.jupiter.engine) 22 | testRuntimeOnly(mnTest.junit.platform.launcher) 23 | } 24 | 25 | configurations { 26 | // Exclude Groovy from the nativeTestCompilation classpath 27 | nativeImageTestClasspath { 28 | exclude module: 'groovy-test' 29 | } 30 | } 31 | 32 | def openGraalModules = [ 33 | "org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk", 34 | "org.graalvm.nativeimage.builder/com.oracle.svm.core.configure", 35 | "org.graalvm.sdk/org.graalvm.nativeimage.impl" 36 | ] 37 | 38 | graalvmNative { 39 | toolchainDetection = false 40 | metadataRepository { 41 | enabled = true 42 | } 43 | binaries { 44 | all { 45 | openGraalModules.each { module -> 46 | jvmArgs.add("--add-exports=" + module + "=ALL-UNNAMED") 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cache-hazelcast/src/test/groovy/io/micronaut/cache/hazelcast/HazelcastClientSupport.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.hazelcast 17 | 18 | import com.hazelcast.client.HazelcastClient 19 | import com.hazelcast.client.config.ClientConfig 20 | import com.hazelcast.core.HazelcastInstance 21 | import org.testcontainers.containers.GenericContainer 22 | 23 | trait HazelcastClientSupport { 24 | 25 | abstract GenericContainer getHazelcast() 26 | 27 | abstract HazelcastInstance getHazelcastServerInstance() 28 | abstract void setHazelcastServerInstance(HazelcastInstance hazelcastInstance) 29 | 30 | def setupSpec() { 31 | ClientConfig clientConfig = new ClientConfig(); 32 | clientConfig.setClusterName("dev"); 33 | clientConfig.getNetworkConfig().addAddress("127.0.0.1:${getHazelcast().firstMappedPort}"); 34 | setHazelcastServerInstance(HazelcastClient.newHazelcastClient(clientConfig)) 35 | } 36 | 37 | def cleanupSpec() { 38 | getHazelcastServerInstance().shutdown() 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: Publish snapshot release 7 | on: [workflow_dispatch] 8 | jobs: 9 | build: 10 | if: github.repository != 'micronaut-projects/micronaut-project-template' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Remove system JDKs 14 | run: | 15 | sudo rm -rf /usr/lib/jvm/* 16 | unset JAVA_HOME 17 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 18 | - uses: actions/checkout@v6 19 | - uses: actions/cache@v4 20 | with: 21 | path: ~/.gradle/caches 22 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 23 | restore-keys: | 24 | ${{ runner.os }}-gradle- 25 | - name: Set up JDK 26 | uses: actions/setup-java@v5 27 | with: 28 | distribution: 'temurin' 29 | java-version: | 30 | 21 31 | 25 32 | - name: Publish to Sonatype Snapshots 33 | if: success() 34 | env: 35 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 36 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 37 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 38 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 39 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 40 | run: ./gradlew publishToSonatype --no-daemon 41 | -------------------------------------------------------------------------------- /cache-ehcache/src/test/groovy/io/micronaut/cache/ehcache/EhcacheConfigurationSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache 17 | 18 | import io.micronaut.cache.ehcache.configuration.EhcacheConfiguration 19 | import io.micronaut.context.ApplicationContext 20 | import spock.lang.Specification 21 | 22 | class EhcacheConfigurationSpec extends Specification{ 23 | 24 | void "it creates cache configurations"() { 25 | given: 26 | ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [ 27 | "ehcache.caches.foo.keyType": "java.lang.Long", 28 | "ehcache.caches.foo.valueType": "java.lang.String" 29 | ]) 30 | 31 | when: 32 | Collection ehcacheConfigurations = ctx.getBeansOfType(EhcacheConfiguration) 33 | 34 | then: 35 | ehcacheConfigurations.size() == 1 36 | ehcacheConfigurations.first().getName() == 'foo' 37 | ehcacheConfigurations.first().getKeyType() == Long 38 | ehcacheConfigurations.first().getValueType() == String 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/interceptor/ValueSupplierException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.interceptor; 17 | 18 | /** 19 | * An exception thrown when the Supplier of a cache value causes an exception. 20 | * 21 | * @author Graeme Rocher 22 | * @since 1.0 23 | */ 24 | class ValueSupplierException extends RuntimeException { 25 | 26 | private final Object key; 27 | 28 | /** 29 | * Create a new exception with the key and cause. 30 | * 31 | * @param key The key for the given annotated element and parameters 32 | * @param cause the cause (which is saved for later retrieval by the 33 | * {@link #getCause()} method). 34 | */ 35 | ValueSupplierException(Object key, RuntimeException cause) { 36 | super(cause); 37 | this.key = key; 38 | } 39 | 40 | @Override 41 | public synchronized RuntimeException getCause() { 42 | return (RuntimeException) super.getCause(); 43 | } 44 | 45 | /** 46 | * @return The cache key 47 | */ 48 | public Object getKey() { 49 | return key; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cache-infinispan/src/main/java/io/micronaut/cache/infinispan/MicronautExecutorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.infinispan; 17 | 18 | import io.micronaut.scheduling.TaskExecutors; 19 | import jakarta.inject.Named; 20 | import jakarta.inject.Singleton; 21 | import org.infinispan.commons.executors.ExecutorFactory; 22 | 23 | import java.util.Properties; 24 | import java.util.concurrent.ExecutorService; 25 | 26 | /** 27 | * An {@link ExecutorFactory} implementation based on the existing IO {@link ExecutorService} bean. 28 | * 29 | * @author Álvaro Sánchez-Mariscal 30 | * @since 1.0.0 31 | */ 32 | @Singleton 33 | public class MicronautExecutorFactory implements ExecutorFactory { 34 | 35 | private ExecutorService executorService; 36 | 37 | /** 38 | * @param executorService the executor service 39 | */ 40 | public MicronautExecutorFactory(@Named(TaskExecutors.IO) ExecutorService executorService) { 41 | this.executorService = executorService; 42 | } 43 | 44 | @Override 45 | public ExecutorService getExecutor(Properties p) { 46 | return executorService; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cache-caffeine/src/main/java/io/micronaut/cache/caffeine/DefaultCacheConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.caffeine; 17 | 18 | import io.micronaut.cache.CacheConfiguration; 19 | import io.micronaut.cache.caffeine.configuration.CaffeineCacheConfiguration; 20 | import io.micronaut.context.annotation.EachProperty; 21 | import io.micronaut.context.annotation.Parameter; 22 | import io.micronaut.runtime.ApplicationConfiguration; 23 | 24 | /** 25 | * Default cache configuration implementation used to configure instances of {@link DefaultSyncCache}. 26 | * 27 | * @author graemerocher 28 | * @since 1.0.2 29 | */ 30 | @EachProperty(CacheConfiguration.PREFIX) 31 | public class DefaultCacheConfiguration extends CaffeineCacheConfiguration { 32 | 33 | /** 34 | * Creates a new cache with the given name. 35 | * 36 | * @param cacheName Name or key of the cache 37 | * @param applicationConfiguration The common application configuration 38 | */ 39 | public DefaultCacheConfiguration(@Parameter String cacheName, ApplicationConfiguration applicationConfiguration) { 40 | super(cacheName, applicationConfiguration); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cache-hazelcast/src/test/groovy/io/micronaut/cache/hazelcast/HazelcastClientSyncCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.hazelcast 17 | 18 | import com.hazelcast.core.HazelcastInstance 19 | import io.micronaut.cache.tck.AbstractSyncCacheSpec 20 | import io.micronaut.context.ApplicationContext 21 | import org.testcontainers.containers.GenericContainer 22 | import org.testcontainers.spock.Testcontainers 23 | import spock.lang.Shared 24 | 25 | /** 26 | * @author Nirav Assar 27 | * @since 1.0 28 | */ 29 | @Testcontainers 30 | class HazelcastClientSyncCacheSpec extends AbstractSyncCacheSpec implements HazelcastClientSupport { 31 | 32 | @Shared 33 | GenericContainer hazelcast = new GenericContainer("hazelcast/hazelcast:" + System.getProperty('hazelcastVersion')) 34 | .withExposedPorts(5701) 35 | 36 | @Shared 37 | HazelcastInstance hazelcastServerInstance 38 | 39 | 40 | @Override 41 | ApplicationContext createApplicationContext() { 42 | return ApplicationContext.run( 43 | "hazelcast.client.clusterName": 'dev', 44 | "hazelcast.client.network.addresses": ["127.0.0.1:${hazelcast.firstMappedPort}"] 45 | ) 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /cache-infinispan/src/main/java/io/micronaut/cache/infinispan/InfinispanCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.infinispan; 17 | 18 | import io.micronaut.context.annotation.Bean; 19 | import io.micronaut.context.annotation.Factory; 20 | import jakarta.inject.Singleton; 21 | import org.infinispan.client.hotrod.RemoteCacheManager; 22 | 23 | /** 24 | * Factory class that creates an Infinispan {@link RemoteCacheManager}. 25 | * 26 | * @author Álvaro Sánchez-Mariscal 27 | * @since 1.0.0 28 | * @see InfinispanHotRodClientConfiguration#getBuilder() 29 | */ 30 | @Factory 31 | public class InfinispanCacheFactory { 32 | 33 | private InfinispanHotRodClientConfiguration configuration; 34 | 35 | /** 36 | * @param configuration the Infinispan client configuration 37 | */ 38 | public InfinispanCacheFactory(InfinispanHotRodClientConfiguration configuration) { 39 | this.configuration = configuration; 40 | } 41 | 42 | /** 43 | * @return a {@link RemoteCacheManager} instance. 44 | */ 45 | @Singleton 46 | @Bean(preDestroy = "close") 47 | RemoteCacheManager remoteCacheManager() { 48 | return new RemoteCacheManager(configuration.getBuilder().build()); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/interceptor/KotlinSuspendFunCacheKeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.interceptor; 17 | 18 | import io.micronaut.core.annotation.AnnotationMetadata; 19 | import io.micronaut.core.annotation.Introspected; 20 | 21 | import java.util.Arrays; 22 | 23 | /** 24 | *

An implementation of the {@link CacheKeyGenerator} which works exactly like {@link DefaultCacheKeyGenerator} but drops the last parameter. 25 | *

26 | * 27 | * @author Jacek Gajek 28 | * @since 3.2.3 29 | * @deprecated Not used, the params are correctly calculated in {@link CacheInterceptor} 30 | */ 31 | @Deprecated(forRemoval = true, since = "5.3.0") 32 | @Introspected 33 | public class KotlinSuspendFunCacheKeyGenerator extends DefaultCacheKeyGenerator { 34 | 35 | @Override 36 | public Object generateKey(AnnotationMetadata annotationMetadata, Object... params) { 37 | if (params == null || params.length == 0) { 38 | return super.generateKey(annotationMetadata, params); 39 | } else { 40 | Object[] usableParams = Arrays.copyOfRange(params, 0, params.length - 1); 41 | return super.generateKey(annotationMetadata, usableParams); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/CounterService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import io.micronaut.cache.annotation.CacheConfig; 19 | import io.micronaut.cache.annotation.CacheInvalidate; 20 | import io.micronaut.cache.annotation.CachePut; 21 | import io.micronaut.cache.annotation.Cacheable; 22 | import jakarta.inject.Singleton; 23 | 24 | import java.util.LinkedHashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * @author Graeme Rocher 29 | * @since 1.0 30 | */ 31 | @Singleton 32 | @CacheConfig(cacheNames = {"counter"}) 33 | public class CounterService { 34 | 35 | Map counters = new LinkedHashMap<>(); 36 | 37 | @CachePut 38 | public int increment(String name) { 39 | int value = counters.computeIfAbsent(name, s -> 0); 40 | counters.put(name, ++value); 41 | return value; 42 | } 43 | 44 | @Cacheable 45 | public int getValue(String name) { 46 | return counters.computeIfAbsent(name, s -> 0); 47 | } 48 | 49 | 50 | @CacheInvalidate() 51 | public void reset(String name) { 52 | counters.remove(name); 53 | } 54 | 55 | @CacheInvalidate 56 | public void set(String name, int val) { 57 | counters.put(name, val); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/central-sync.yml: -------------------------------------------------------------------------------- 1 | # WARNING: Do not edit this file directly. Instead, go to: 2 | # 3 | # https://github.com/micronaut-projects/micronaut-project-template/tree/master/.github/workflows 4 | # 5 | # and edit them there. Note that it will be sync'ed to all the Micronaut repos 6 | name: Maven Central Sync 7 | on: 8 | workflow_dispatch: 9 | inputs: 10 | release_version: 11 | description: 'Release version (eg: 1.2.3)' 12 | required: true 13 | jobs: 14 | central-sync: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Remove system JDKs 18 | run: | 19 | sudo rm -rf /usr/lib/jvm/* 20 | unset JAVA_HOME 21 | export PATH=$(echo "$PATH" | tr ':' '\n' | grep -v '/usr/lib/jvm' | paste -sd:) 22 | - name: Checkout repository 23 | uses: actions/checkout@v6 24 | with: 25 | ref: v${{ github.event.inputs.release_version }} 26 | - uses: gradle/actions/wrapper-validation@v5 27 | - name: Set up JDK 28 | uses: actions/setup-java@v5 29 | with: 30 | distribution: 'temurin' 31 | java-version: | 32 | 21 33 | 25 34 | - name: Publish to Sonatype OSSRH 35 | env: 36 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 37 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 38 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 39 | GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} 40 | GPG_FILE: ${{ secrets.GPG_FILE }} 41 | DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} 42 | DEVELOCITY_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USERNAME }} 43 | DEVELOCITY_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} 44 | run: | 45 | echo $GPG_FILE | base64 -d > secring.gpg 46 | ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository 47 | -------------------------------------------------------------------------------- /cache-core/src/test/groovy/io/micronaut/cache/serialize/DefaultStringKeySerializerSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache.serialize 2 | 3 | import io.micronaut.core.convert.DefaultMutableConversionService 4 | import spock.lang.Issue 5 | import spock.lang.Specification 6 | 7 | import java.nio.charset.Charset 8 | import java.time.LocalDate 9 | import java.time.LocalDateTime 10 | import java.time.OffsetDateTime 11 | import java.time.OffsetTime 12 | import java.time.ZoneId 13 | import java.time.ZoneOffset 14 | import java.time.ZonedDateTime 15 | 16 | class DefaultStringKeySerializerSpec extends Specification { 17 | 18 | @Issue("https://github.com/micronaut-projects/micronaut-cache/issues/404") 19 | void "test TemporalAccessor serialization"() { 20 | 21 | given: 22 | final serializer = new DefaultStringKeySerializer("cacheName", Charset.defaultCharset(), new DefaultMutableConversionService()) 23 | 24 | expect: 25 | new String(serializer.serialize(sourceObject).get(), Charset.defaultCharset()) == "cacheName:$result" 26 | 27 | where: 28 | sourceObject | result 29 | LocalDate.of(2022, 8, 12) | "2022-08-12" 30 | LocalDateTime.of(2022, 8, 12, 12, 19) | "2022-08-12T12:19:00" 31 | OffsetTime.of(12, 19, 0, 0, ZoneOffset.ofHours(5)) | "12:19:00+05:00" 32 | OffsetDateTime.of(2022, 8, 12, 12, 19, 0, 0, ZoneOffset.ofHours(5)) | "2022-08-12T12:19:00+05:00" 33 | ZonedDateTime.of(2022, 8, 12, 12, 19, 0, 0, ZoneOffset.ofHours(5)) | "2022-08-12T12:19:00+05:00" 34 | ZonedDateTime.of(2022, 8, 12, 12, 19, 0, 0, ZoneId.of("Europe/Berlin")) | "2022-08-12T12:19:00+02:00[Europe/Berlin]" 35 | LocalDateTime.of(2022, 8, 12, 12, 19).toInstant(ZoneOffset.UTC) | "2022-08-12T12:19:00Z" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/java/io/micronaut/cache/DynamicCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import io.micronaut.core.type.Argument; 19 | import org.jspecify.annotations.NonNull; 20 | 21 | import java.util.Map; 22 | import java.util.Optional; 23 | import java.util.function.Supplier; 24 | 25 | public class DynamicCache implements SyncCache { 26 | 27 | @NonNull 28 | @Override 29 | public Optional get(@NonNull Object key, @NonNull Argument requiredType) { 30 | return Optional.empty(); 31 | } 32 | 33 | @Override 34 | public T get(@NonNull Object key, @NonNull Argument requiredType, @NonNull Supplier supplier) { 35 | return null; 36 | } 37 | 38 | @NonNull 39 | @Override 40 | public Optional putIfAbsent(@NonNull Object key, @NonNull T value) { 41 | return Optional.empty(); 42 | } 43 | 44 | @Override 45 | public void put(@NonNull Object key, @NonNull Object value) { 46 | 47 | } 48 | 49 | @Override 50 | public void invalidate(@NonNull Object key) { 51 | 52 | } 53 | 54 | @Override 55 | public void invalidateAll() { 56 | 57 | } 58 | 59 | @Override 60 | public String getName() { 61 | return null; 62 | } 63 | 64 | @Override 65 | public Map getNativeCache() { 66 | return null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micronaut Cache 2 | 3 | [![Maven Central](https://img.shields.io/maven-central/v/io.micronaut.cache/micronaut-cache-ehcache.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.micronaut.cache%22%20AND%20a:%22micronaut-cache-ehcache%22) 4 | [![Build Status](https://github.com/micronaut-projects/micronaut-cache/workflows/Java%20CI/badge.svg)](https://github.com/micronaut-projects/micronaut-cache/actions) 5 | [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.micronaut.io/scans) 6 | 7 | This project includes caching support for [Micronaut](http://micronaut.io). 8 | 9 | ## Documentation 10 | 11 | See the [stable](https://micronaut-projects.github.io/micronaut-cache/latest/guide) or [snapshot](https://micronaut-projects.github.io/micronaut-cache/snapshot/guide) documentation for more information. 12 | 13 | See the [Snapshot Documentation](https://micronaut-projects.github.io/micronaut-cache/snapshot/guide) for the 14 | current development docs. 15 | 16 | ## Snapshots and Releases 17 | 18 | Snaphots are automatically published to [JFrog OSS](https://oss.jfrog.org/artifactory/oss-snapshot-local/) using [Github Actions](https://github.com/micronaut-projects/micronaut-openapi/actions). 19 | 20 | See the documentation in the [Micronaut Docs](https://docs.micronaut.io/latest/guide/index.html#usingsnapshots) for how to configure your build to use snapshots. 21 | 22 | Releases are published to JCenter and Maven Central via [Github Actions](https://github.com/micronaut-projects/micronaut-cache/actions). 23 | 24 | A release is performed with the following steps: 25 | 26 | * [Publish the draft release](https://github.com/micronaut-projects/micronaut-cache/releases). There should be already a draft release created, edit and publish it. The Git Tag should start with `v`. For example `v1.0.0`. 27 | * [Monitor the Workflow](https://github.com/micronaut-projects/micronaut-cache/actions?query=workflow%3ARelease) to check it passed successfully. 28 | * Celebrate! -------------------------------------------------------------------------------- /test-suite-hazelcast-kotlin/src/test/kotlin/io/micronaut/cache/HazelcastTest.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.hazelcast.HazelcastCacheManager 4 | import io.micronaut.http.client.HttpClient 5 | import io.micronaut.http.client.annotation.Client 6 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest 7 | import io.micronaut.test.support.TestPropertyProvider 8 | import jakarta.inject.Inject 9 | import org.junit.jupiter.api.Assertions 10 | import org.junit.jupiter.api.Assertions.assertEquals 11 | import org.junit.jupiter.api.Test 12 | import org.testcontainers.containers.GenericContainer 13 | import org.testcontainers.junit.jupiter.Container 14 | import org.testcontainers.junit.jupiter.Testcontainers 15 | 16 | @MicronautTest 17 | @Testcontainers 18 | internal class HazelcastTest : TestPropertyProvider { 19 | 20 | @Container 21 | val hazelcast: GenericContainer<*> = 22 | GenericContainer("hazelcast/hazelcast:" + System.getProperty("hazelcastVersion")) 23 | .withExposedPorts(5701) 24 | 25 | @Inject 26 | @field:Client("/") 27 | lateinit var httpClient: HttpClient 28 | 29 | @Inject 30 | lateinit var cacheManager: HazelcastCacheManager 31 | 32 | @Test 33 | fun simpleTest() { 34 | val client = httpClient.toBlocking() 35 | 36 | var inc = client.retrieve("/inc", Int::class.java) 37 | assertEquals(1, inc) 38 | 39 | inc = client.retrieve("/inc", Int::class.java) 40 | assertEquals(2, inc) 41 | 42 | val get = client.retrieve("/get", Int::class.java) 43 | assertEquals(2, get) 44 | assertEquals(2, cacheManager.getCache("counter").get("test", Int::class.java).get()) 45 | 46 | client.exchange("/del") 47 | 48 | Assertions.assertTrue(cacheManager.getCache("counter").get("test", Int::class.java).isEmpty) 49 | } 50 | 51 | override fun getProperties() = mapOf( 52 | "hazelcast.client.network.addresses" to "${hazelcast.host}:${hazelcast.firstMappedPort}" 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /cache-hazelcast/src/test/groovy/io/micronaut/cache/hazelcast/HazelcastMemberSyncCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.hazelcast 17 | 18 | import com.hazelcast.config.Config 19 | import com.hazelcast.config.EvictionPolicy 20 | import com.hazelcast.config.MapConfig 21 | import com.hazelcast.config.MaxSizePolicy 22 | import io.micronaut.cache.tck.AbstractSyncCacheSpec 23 | import io.micronaut.context.ApplicationContext 24 | import io.micronaut.context.event.BeanCreatedEvent 25 | import io.micronaut.context.event.BeanCreatedEventListener 26 | import jakarta.inject.Singleton 27 | 28 | /** 29 | * @since 1.0 30 | */ 31 | class HazelcastMemberSyncCacheSpec extends AbstractSyncCacheSpec { 32 | 33 | @Singleton 34 | static class CustomConfig implements BeanCreatedEventListener { 35 | @Override 36 | Config onCreated(BeanCreatedEvent event) { 37 | MapConfig mapConfig = new MapConfig().setName("test") 38 | mapConfig.getEvictionConfig() 39 | .setMaxSizePolicy(MaxSizePolicy.PER_PARTITION) 40 | .setSize(3) 41 | .setEvictionPolicy(EvictionPolicy.LRU) 42 | event.getBean().addMapConfig(mapConfig) 43 | event.getBean() 44 | } 45 | } 46 | 47 | @Override 48 | ApplicationContext createApplicationContext() { 49 | return ApplicationContext.run() 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/interceptor/DefaultCacheKeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.interceptor; 17 | 18 | import io.micronaut.core.annotation.AnnotationMetadata; 19 | import io.micronaut.core.annotation.Introspected; 20 | import io.micronaut.core.util.ArrayUtils; 21 | 22 | /** 23 | *

A default implementation of the {@link CacheKeyGenerator} interface that uses the parameters of the method only.

24 | *

25 | *

This implementation is appropriate for most common cases but note that collisions can occur for classes that 26 | * use the same cache and have overlapping signatures as the default implementation does not use the method itself 27 | * when generating the key

28 | * 29 | * @author Graeme Rocher 30 | * @since 1.0 31 | */ 32 | @Introspected 33 | public class DefaultCacheKeyGenerator implements CacheKeyGenerator { 34 | 35 | @Override 36 | public Object generateKey(AnnotationMetadata annotationMetadata, Object... params) { 37 | if (ArrayUtils.isEmpty(params)) { 38 | return ParametersKey.ZERO_ARG_KEY; 39 | } 40 | if (params.length == 1) { 41 | Object param = params[0]; 42 | if (param != null && !param.getClass().isArray()) { 43 | return param; 44 | } else { 45 | return new ParametersKey(params); 46 | } 47 | } else { 48 | return new ParametersKey(params); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/groovy/io/micronaut/cache/SyncCacheJavaSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import spock.lang.Specification 20 | 21 | /** 22 | * @author Graeme Rocher 23 | * @since 1.0 24 | */ 25 | class SyncCacheJavaSpec extends Specification { 26 | void "test cacheable annotations"() { 27 | given: 28 | ApplicationContext applicationContext = ApplicationContext.run( 29 | 'micronaut.caches.counter.initialCapacity':10, 30 | 'micronaut.caches.counter.maximumSize':20, 31 | 'micronaut.caches.counter.testMode':true 32 | ) 33 | 34 | when: 35 | CounterService counterService = applicationContext.getBean(CounterService) 36 | def result =counterService.increment("test") 37 | 38 | then: 39 | result == 1 40 | counterService.getValue("test") == 1 41 | counterService.getValue("test") == 1 42 | 43 | when: 44 | result = counterService.incrementNoCache("test") 45 | 46 | then: 47 | result == 2 48 | counterService.getValue("test") == 1 49 | 50 | when: 51 | counterService.reset("test") 52 | then: 53 | counterService.getValue("test") == 0 54 | 55 | when: 56 | result = counterService.increment("test") 57 | 58 | then: 59 | result == 1 60 | counterService.getValue("test") == 1 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test-suite-hazelcast-groovy/src/test/groovy/io/micronaut/cache/HazelcastSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.hazelcast.HazelcastCacheManager 4 | import io.micronaut.http.client.BlockingHttpClient 5 | import io.micronaut.http.client.HttpClient 6 | import io.micronaut.http.client.annotation.Client 7 | import io.micronaut.test.extensions.spock.annotation.MicronautTest 8 | import io.micronaut.test.support.TestPropertyProvider 9 | import jakarta.inject.Inject 10 | import org.testcontainers.containers.GenericContainer 11 | import spock.lang.AutoCleanup 12 | import spock.lang.Shared 13 | import spock.lang.Specification 14 | 15 | @MicronautTest 16 | class HazelcastSpec extends Specification implements TestPropertyProvider { 17 | 18 | @AutoCleanup 19 | @Shared 20 | GenericContainer hazelcast = new GenericContainer<>("hazelcast/hazelcast:" + System.getProperty("hazelcastVersion")) 21 | .withExposedPorts(5701) 22 | 23 | @Inject 24 | @Client("/") 25 | HttpClient httpClient; 26 | 27 | @Inject 28 | HazelcastCacheManager cacheManager; 29 | 30 | void "simple test"() { 31 | given: 32 | BlockingHttpClient client = httpClient.toBlocking(); 33 | 34 | when: 35 | Integer inc = client.retrieve("/inc", Integer.class); 36 | 37 | then: 38 | inc == 1 39 | 40 | when: 41 | inc = client.retrieve("/inc", Integer.class); 42 | 43 | then: 44 | inc == 2 45 | 46 | when: 47 | Integer get = client.retrieve("/get", Integer.class); 48 | 49 | then: 50 | get == 2 51 | cacheManager.getCache("counter").get("test", Integer.class).get() == 2 52 | 53 | when: 54 | client.exchange("/del"); 55 | 56 | then: 57 | cacheManager.getCache("counter").get("test", Integer.class).empty 58 | } 59 | 60 | @Override 61 | Map getProperties() { 62 | if (!hazelcast.running) { 63 | hazelcast.start() 64 | } 65 | ["hazelcast.client.network.addresses": "${hazelcast.host}:${hazelcast.firstMappedPort}"] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/NativeCaffeineTest.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.context.BeanContext; 4 | import io.micronaut.context.annotation.Property; 5 | import io.micronaut.http.client.BlockingHttpClient; 6 | import io.micronaut.http.client.HttpClient; 7 | import io.micronaut.http.client.annotation.Client; 8 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 9 | import jakarta.inject.Inject; 10 | import org.junit.jupiter.api.Disabled; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | @MicronautTest 19 | @Property(name = "micronaut.caches.counter.initial-capacity", value = "10") 20 | @Property(name = "micronaut.caches.counter.test-mode", value = "true") 21 | @Property(name = "micronaut.caches.counter.maximum-size", value = "20") 22 | @Property(name = "micronaut.caches.counter.listen-to-removals", value = "true") 23 | class NativeCaffeineTest { 24 | 25 | @Inject 26 | @Client("/") 27 | HttpClient httpClient; 28 | 29 | @Inject 30 | BeanContext ctx; 31 | 32 | @Test 33 | void simpleTest() { 34 | BlockingHttpClient client = httpClient.toBlocking(); 35 | RemovalListenerImpl bean = ctx.getBean(RemovalListenerImpl.class); 36 | 37 | Integer inc = client.retrieve("/inc", Integer.class); 38 | assertEquals(1, inc); 39 | 40 | assertTrue(() -> bean.getRemovals().isEmpty()); 41 | 42 | inc = client.retrieve("/inc", Integer.class); 43 | assertEquals(2, inc); 44 | 45 | List expectedEventsAfterReplacement = List.of("test|1|REPLACED"); 46 | assertEquals(expectedEventsAfterReplacement, bean.getRemovals()); 47 | 48 | Integer get = client.retrieve("/get", Integer.class); 49 | assertEquals(2, get); 50 | 51 | client.exchange("/del"); 52 | 53 | List expectedEventsAfterRemoval = List.of("test|1|REPLACED", "test|2|EXPLICIT"); 54 | assertEquals(expectedEventsAfterRemoval, bean.getRemovals()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cache-ehcache/src/test/groovy/io/micronaut/cache/ehcache/EhcacheCacheManagerFactorySpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import org.ehcache.CacheManager 20 | import org.ehcache.Status 21 | import org.ehcache.core.internal.statistics.DefaultStatisticsService 22 | import org.ehcache.core.spi.service.StatisticsService 23 | import spock.lang.Specification 24 | 25 | class EhcacheCacheManagerFactorySpec extends Specification { 26 | 27 | void "it creates a cache manager and initialises it"() { 28 | given: 29 | ApplicationContext ctx = ApplicationContext.run(["ehcache.enabled": true]) 30 | 31 | when: 32 | CacheManager cacheManager = ctx.getBean(CacheManager) 33 | 34 | then: 35 | cacheManager.status == Status.AVAILABLE 36 | 37 | cleanup: 38 | ctx.close() 39 | } 40 | 41 | void "it creates an statistics service"() { 42 | given: 43 | ApplicationContext ctx = ApplicationContext.run(["ehcache.caches.foo.enabled": true]) 44 | 45 | when: 46 | DefaultStatisticsService statisticsService = (DefaultStatisticsService) ctx.getBean(StatisticsService) 47 | def manager = ctx.getBean(io.micronaut.cache.CacheManager) //Triggering CacheManager initialisation 48 | manager.getCache("foo").put("cheese", "cheddar") 49 | 50 | then: 51 | statisticsService.getCacheStatistics("foo").cachePuts == 1 52 | 53 | cleanup: 54 | ctx.close() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cache-ehcache/src/main/java/io/micronaut/cache/ehcache/configuration/EhcacheClusterResourcePoolConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache.configuration; 17 | 18 | import io.micronaut.context.annotation.EachProperty; 19 | import io.micronaut.context.annotation.Parameter; 20 | import org.jspecify.annotations.NonNull; 21 | import io.micronaut.core.naming.Named; 22 | 23 | /** 24 | * Resource pool configurations. 25 | */ 26 | @EachProperty(EhcacheClusterResourcePoolConfiguration.PREFIX) 27 | public class EhcacheClusterResourcePoolConfiguration extends AbstractResourcePoolConfiguration implements Named { 28 | public static final String PREFIX = EhcacheCacheManagerConfiguration.PREFIX + "." + EhcacheCacheManagerConfiguration.EhcacheClusterConfiguration.PREFIX + ".resource-pools"; 29 | 30 | private final String name; 31 | private String serverResource; 32 | 33 | /** 34 | * @param name the resource pool name 35 | */ 36 | public EhcacheClusterResourcePoolConfiguration(@Parameter String name) { 37 | this.name = name; 38 | } 39 | 40 | @NonNull 41 | @Override 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | /** 47 | * @return the server resource 48 | */ 49 | public String getServerResource() { 50 | return serverResource; 51 | } 52 | 53 | /** 54 | * @param serverResource the server resource 55 | */ 56 | public void setServerResource(String serverResource) { 57 | this.serverResource = serverResource; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/java/io/micronaut/cache/CounterService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import io.micronaut.cache.annotation.CacheConfig; 19 | import io.micronaut.cache.annotation.CacheInvalidate; 20 | import io.micronaut.cache.annotation.CachePut; 21 | import io.micronaut.cache.annotation.Cacheable; 22 | import jakarta.inject.Singleton; 23 | 24 | import java.util.LinkedHashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * @author Graeme Rocher 29 | * @since 1.0 30 | */ 31 | @Singleton 32 | @CacheConfig(cacheNames = {"counter"}) 33 | public class CounterService { 34 | Map counters = new LinkedHashMap<>(); 35 | 36 | public int incrementNoCache(String name) { 37 | int value = counters.computeIfAbsent(name, s -> 0); 38 | counters.put(name, ++value); 39 | return value; 40 | } 41 | 42 | @CachePut 43 | public int increment(String name) { 44 | int value = counters.computeIfAbsent(name, s -> 0); 45 | counters.put(name, ++value); 46 | return value; 47 | } 48 | 49 | @Cacheable 50 | public int getValue(String name) { 51 | return counters.computeIfAbsent(name, s -> 0); 52 | } 53 | 54 | 55 | @CacheInvalidate(all = true) 56 | public void reset() { 57 | counters.clear(); 58 | } 59 | 60 | @CacheInvalidate() 61 | public void reset(String name) { 62 | counters.remove(name); 63 | } 64 | @CacheInvalidate 65 | public void set(String name, int val) { 66 | counters.put(name, val); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/AsyncCacheErrorHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache; 17 | 18 | import io.micronaut.cache.interceptor.CacheInterceptor; 19 | import jakarta.inject.Named; 20 | import jakarta.inject.Singleton; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | /** 25 | * Async error handler that simply logs errors. 26 | * 27 | * @author Graeme Rocher 28 | * @since 1.0 29 | */ 30 | @Singleton 31 | @Named("async") 32 | public class AsyncCacheErrorHandler implements CacheErrorHandler { 33 | 34 | private static final Logger LOG = LoggerFactory.getLogger(CacheInterceptor.class); 35 | 36 | @Override 37 | public boolean handleInvalidateError(Cache cache, Object key, RuntimeException e) { 38 | if (LOG.isErrorEnabled()) { 39 | LOG.error("Error invalidating cache [" + cache.getName() + "] for key: " + key, e); 40 | } 41 | return false; 42 | } 43 | 44 | @Override 45 | public boolean handleInvalidateError(Cache cache, RuntimeException e) { 46 | if (LOG.isErrorEnabled()) { 47 | LOG.error("Error invalidating cache: " + cache.getName(), e); 48 | } 49 | return false; 50 | } 51 | 52 | @Override 53 | public boolean handlePutError(Cache cache, Object key, Object result, RuntimeException e) { 54 | if (LOG.isErrorEnabled()) { 55 | LOG.error("Error caching value [" + result + "] for key [" + key + "] in cache: " + cache.getName(), e); 56 | } 57 | return false; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cache-ehcache/src/main/java/io/micronaut/cache/ehcache/serialization/CharSequenceSerializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.ehcache.serialization; 17 | 18 | import org.ehcache.impl.serialization.StringSerializer; 19 | import org.ehcache.spi.serialization.Serializer; 20 | import org.ehcache.spi.serialization.SerializerException; 21 | 22 | import java.nio.ByteBuffer; 23 | 24 | /** 25 | * A {@link org.ehcache.spi.serialization.Serializer} implementation for {@link java.lang.CharSequence} that delegates 26 | * to {@link org.ehcache.impl.serialization.StringSerializer}. 27 | * 28 | * @author Álvaro Sánchez-Mariscal 29 | * @since 1.0.0 30 | */ 31 | public class CharSequenceSerializer implements Serializer { 32 | 33 | private final StringSerializer stringSerializer; 34 | 35 | /** 36 | * Default constructor. 37 | */ 38 | public CharSequenceSerializer() { 39 | this.stringSerializer = new StringSerializer(); 40 | } 41 | 42 | @Override 43 | public ByteBuffer serialize(CharSequence object) throws SerializerException { 44 | return stringSerializer.serialize(object.toString()); 45 | } 46 | 47 | @Override 48 | public CharSequence read(ByteBuffer binary) throws ClassNotFoundException, SerializerException { 49 | return stringSerializer.read(binary); 50 | } 51 | 52 | @Override 53 | public boolean equals(CharSequence object, ByteBuffer binary) throws ClassNotFoundException, SerializerException { 54 | return stringSerializer.equals(object.toString(), binary); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for reporting an issue, please review the task list below before submitting the issue. Your issue report will be closed if the issue is incomplete and the below tasks not completed. 8 | 9 | NOTE: If you are unsure about something and the issue is more of a question a better place to ask questions is on Github Discussions :arrow_up:, [Stack Overflow](https://stackoverflow.com/tags/micronaut) or [Gitter](https://gitter.im/micronautfw/). 10 | - type: textarea 11 | attributes: 12 | label: Expected Behavior 13 | description: A concise description of what you expected to happen. 14 | placeholder: Tell us what should happen 15 | validations: 16 | required: false 17 | - type: textarea 18 | attributes: 19 | label: Actual Behaviour 20 | description: A concise description of what you're experiencing. 21 | placeholder: Tell us what happens instead 22 | validations: 23 | required: false 24 | - type: textarea 25 | attributes: 26 | label: Steps To Reproduce 27 | description: Steps to reproduce the behavior. 28 | placeholder: | 29 | 1. In this environment... 30 | 2. With this config... 31 | 3. Run '...' 32 | 4. See error... 33 | validations: 34 | required: false 35 | - type: textarea 36 | attributes: 37 | label: Environment Information 38 | description: Environment information where the problem occurs. 39 | placeholder: | 40 | - Operating System: 41 | - JDK Version: 42 | validations: 43 | required: false 44 | - type: input 45 | id: example 46 | attributes: 47 | label: Example Application 48 | description: Example application link. 49 | placeholder: | 50 | Link to GitHub repository with an example that reproduces the issue 51 | validations: 52 | required: false 53 | - type: input 54 | id: version 55 | attributes: 56 | label: Version 57 | description: Micronaut version 58 | validations: 59 | required: true 60 | 61 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/annotation/CacheConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.annotation; 17 | 18 | import io.micronaut.cache.interceptor.CacheKeyGenerator; 19 | import io.micronaut.cache.interceptor.DefaultCacheKeyGenerator; 20 | import io.micronaut.context.annotation.AliasFor; 21 | 22 | import java.lang.annotation.Documented; 23 | import java.lang.annotation.ElementType; 24 | import java.lang.annotation.Inherited; 25 | import java.lang.annotation.Retention; 26 | import java.lang.annotation.RetentionPolicy; 27 | import java.lang.annotation.Target; 28 | 29 | /** 30 | *

An annotation that can be used on either a type or an annotation stereotype to configure common caching 31 | * behaviour.

32 | * 33 | * @author Graeme Rocher 34 | * @since 1.0 35 | */ 36 | @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) 37 | @Retention(RetentionPolicy.RUNTIME) 38 | @Documented 39 | @Inherited 40 | public @interface CacheConfig { 41 | /** 42 | * @return Same as {@link #cacheNames()} 43 | */ 44 | @AliasFor(member = "cacheNames") 45 | String[] value() default {}; 46 | 47 | /** 48 | * Specifies one or many cache names to store cache operation values in. If specified at the type 49 | * level, can be overridden at the method level. 50 | * 51 | * @return The names of the caches to to store values in 52 | */ 53 | String[] cacheNames() default {}; 54 | 55 | /** 56 | * @return The default bean type of the key generator 57 | */ 58 | Class keyGenerator() default DefaultCacheKeyGenerator.class; 59 | } 60 | -------------------------------------------------------------------------------- /test-suite-caffeine-native/src/test/java/io/micronaut/cache/NewsService.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.annotation.CacheInvalidate; 4 | import io.micronaut.cache.annotation.CachePut; 5 | import io.micronaut.cache.annotation.Cacheable; 6 | import jakarta.inject.Singleton; 7 | 8 | import java.time.Month; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @Singleton 17 | public class NewsService { 18 | 19 | Map> headlines = new HashMap<>(Map.of( 20 | Month.NOVEMBER, List.of( 21 | "Micronaut Graduates to Trial Level in Thoughtworks technology radar Vol.1", 22 | "Micronaut AOP: Awesome flexibility without the complexity" 23 | ), 24 | Month.OCTOBER, Collections.singletonList("Micronaut AOP: Awesome flexibility without the complexity") 25 | )); 26 | 27 | @SuppressWarnings("java:S2925") // Sleep is used for testing purposes only 28 | @Cacheable(value = "headlines", parameters = {"month"}) 29 | public List headlines(Month month) { 30 | try { 31 | TimeUnit.SECONDS.sleep(3); 32 | return headlines.get(month); 33 | } catch (InterruptedException e) { 34 | return null; 35 | } 36 | } 37 | 38 | @CachePut(value = "headlines", parameters = {"month"}) 39 | public List addHeadline(Month month, String headline) { 40 | if (headlines.containsKey(month)) { 41 | List l = new ArrayList<>(headlines.get(month)); 42 | l.add(headline); 43 | headlines.put(month, l); 44 | } else { 45 | headlines.put(month, Collections.singletonList(headline)); 46 | } 47 | return headlines.get(month); 48 | } 49 | 50 | @CacheInvalidate(value = "headlines", parameters = {"month"}) 51 | public void removeHeadline(Month month, String headline) { 52 | if (headlines.containsKey(month)) { 53 | List l = new ArrayList<>(headlines.get(month)); 54 | l.remove(headline); 55 | headlines.put(month, l); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test-suite-hazelcast-java/src/test/java/io/micronaut/cache/HazelcastTest.java: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache; 2 | 3 | import io.micronaut.cache.hazelcast.HazelcastCacheManager; 4 | import org.jspecify.annotations.NonNull; 5 | import io.micronaut.http.client.BlockingHttpClient; 6 | import io.micronaut.http.client.HttpClient; 7 | import io.micronaut.http.client.annotation.Client; 8 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest; 9 | import io.micronaut.test.support.TestPropertyProvider; 10 | import jakarta.inject.Inject; 11 | import org.junit.jupiter.api.Test; 12 | import org.testcontainers.containers.GenericContainer; 13 | import org.testcontainers.junit.jupiter.Container; 14 | import org.testcontainers.junit.jupiter.Testcontainers; 15 | 16 | import java.util.Map; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | @MicronautTest 22 | @Testcontainers 23 | class HazelcastTest implements TestPropertyProvider { 24 | 25 | @Container 26 | GenericContainer hazelcast = new GenericContainer<>("hazelcast/hazelcast:" + System.getProperty("hazelcastVersion")) 27 | .withExposedPorts(5701); 28 | 29 | @Inject 30 | @Client("/") 31 | HttpClient httpClient; 32 | 33 | @Inject 34 | HazelcastCacheManager cacheManager; 35 | 36 | @Test 37 | void simpleTest() { 38 | BlockingHttpClient client = httpClient.toBlocking(); 39 | 40 | Integer inc = client.retrieve("/inc", Integer.class); 41 | assertEquals(1, inc); 42 | 43 | inc = client.retrieve("/inc", Integer.class); 44 | assertEquals(2, inc); 45 | 46 | Integer get = client.retrieve("/get", Integer.class); 47 | assertEquals(2, get); 48 | 49 | assertEquals(2, cacheManager.getCache("counter").get("test", Integer.class).get()); 50 | 51 | client.exchange("/del"); 52 | 53 | assertTrue(cacheManager.getCache("counter").get("test", Integer.class).isEmpty()); 54 | } 55 | 56 | @Override 57 | public @NonNull Map getProperties() { 58 | return Map.of( 59 | "hazelcast.client.network.addresses", hazelcast.getHost() + ":" + hazelcast.getFirstMappedPort() 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cache-infinispan/src/main/java/io/micronaut/cache/infinispan/InfinispanSyncCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.infinispan; 17 | 18 | import io.micronaut.cache.AbstractMapBasedSyncCache; 19 | import io.micronaut.cache.AsyncCache; 20 | import io.micronaut.cache.CacheInfo; 21 | import org.jspecify.annotations.NonNull; 22 | import io.micronaut.core.async.publisher.Publishers; 23 | import io.micronaut.core.convert.ConversionService; 24 | import org.infinispan.client.hotrod.RemoteCache; 25 | import org.reactivestreams.Publisher; 26 | 27 | /** 28 | * A {@link io.micronaut.cache.SyncCache} implementation based on Infinispan's {@link RemoteCache}. 29 | * 30 | * @author Álvaro Sánchez-Mariscal 31 | * @since 1.0.0 32 | */ 33 | public class InfinispanSyncCache extends AbstractMapBasedSyncCache> { 34 | 35 | /** 36 | * @param conversionService the conversion service 37 | * @param nativeCache the native cache 38 | */ 39 | public InfinispanSyncCache(ConversionService conversionService, RemoteCache nativeCache) { 40 | super(conversionService, nativeCache); 41 | } 42 | 43 | @Override 44 | public String getName() { 45 | return getNativeCache().getName(); 46 | } 47 | 48 | @NonNull 49 | @Override 50 | public AsyncCache> async() { 51 | return new InfinispanAsyncCache(getNativeCache(), getConversionService()); 52 | } 53 | 54 | @Override 55 | public Publisher getCacheInfo() { 56 | return Publishers.just(new InfinispanCacheInfo(getNativeCache())); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/docs/guide/caffeine.adoc: -------------------------------------------------------------------------------- 1 | To cache using https://github.com/ben-manes/caffeine[Caffeine] add the following dependency to your application: 2 | 3 | NOTE: This module is built and tested with Caffeine {caffeineVersion} 4 | 5 | dependency:io.micronaut.cache:micronaut-cache-caffeine[] 6 | 7 | 8 | Then configure one or many caches. For example with `application.yml`: 9 | 10 | .Cache Configuration Example 11 | [configuration] 12 | ---- 13 | micronaut: 14 | caches: 15 | my-cache: 16 | maximum-size: 20 17 | ---- 18 | 19 | The above example will configure a cache called "my-cache" with a maximum size of 20. 20 | 21 | [configuration] 22 | ---- 23 | micronaut: 24 | caches: 25 | my-cache: 26 | listen-to-removals: true 27 | listen-to-evictions: true 28 | ---- 29 | 30 | This example is a cache with the removal/eviction listeners. To be able to use them just implement the `com.github.benmanes.caffeine.cache.RemovalListener` interface as shown in the example. 31 | 32 | snippet::io.micronaut.cache.RemovalListenerImpl[project-base="test-suite-caffeine",tags="clazz"] 33 | 34 | [NOTE] 35 | .Naming Caches 36 | ==== 37 | Names of caches under `micronaut.caches` should be defined in kebab case (lowercase and hyphen separated), if camel case is used the names are normalized to kebab case. So for example specifying `myCache` will become `my-cache`. The kebab case form should be used when referencing caches in the ann:cache.annotation.Cacheable[] annotation. 38 | ==== 39 | 40 | To configure a weigher to be used with the `maximumWeight` configuration, create a bean that implements `com.github.benmanes.caffeine.cache.Weigher`. To associate a given weigher with only a specific cache, annotate the bean with `@Named()`. Weighers without a named qualifier will apply to all caches that don't have a named weigher. If no beans are found, a default implementation will be used. 41 | 42 | [NOTE] 43 | .Native compilation 44 | ==== 45 | When using Caffeine with Native Compilation, the most commonly used caches will be automatically registered. 46 | If you require additional caches, you will need to register them with Graal yourself https://docs.micronaut.io/latest/guide/#_adding_additional_classes_for_reflective_access[as shown in the guide]. 47 | ==== 48 | -------------------------------------------------------------------------------- /cache-core/src/test/groovy/io/micronaut/cache/CacheConfigWithoutCacheableSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.cache.annotation.CacheConfig 4 | import io.micronaut.cache.jcache.JCacheManager 5 | import io.micronaut.context.ApplicationContext 6 | import io.micronaut.context.annotation.Factory 7 | import io.micronaut.context.annotation.Requires 8 | import jakarta.inject.Singleton 9 | import reactor.core.publisher.Flux 10 | import spock.lang.AutoCleanup 11 | import spock.lang.Shared 12 | import spock.lang.Specification 13 | 14 | import javax.cache.CacheManager 15 | import javax.cache.Caching 16 | import javax.cache.configuration.MutableConfiguration 17 | 18 | class CacheConfigWithoutCacheableSpec extends Specification { 19 | 20 | @Shared 21 | @AutoCleanup 22 | ApplicationContext context = ApplicationContext.run( 23 | (JCacheManager.JCACHE_ENABLED):true, 24 | 'spec.name': 'CacheConfigWithoutCacheableSpec' 25 | ) 26 | 27 | void "it doesn't cache methods if not explicitly annotated"() { 28 | given: 29 | MyService myService = context.getBean(MyService) 30 | 31 | when: 32 | def things = myService.things().toIterable() 33 | 34 | then: 35 | things.size() == 3 36 | } 37 | 38 | @Singleton 39 | @CacheConfig('my-service') 40 | @Requires(property = JCacheManager.JCACHE_ENABLED, value = "true") 41 | @Requires(property = "spec.name", value = "CacheConfigWithoutCacheableSpec") 42 | static class MyService { 43 | 44 | private String[] things = ['one', 'two', 'three'].toArray() 45 | 46 | Flux things() { 47 | Flux.fromArray(things) 48 | } 49 | 50 | } 51 | 52 | @Factory 53 | @Requires(property = JCacheManager.JCACHE_ENABLED, value = "true") 54 | @Requires(property = "spec.name", value = "CacheConfigWithoutCacheableSpec") 55 | static class CacheFactory { 56 | 57 | @Singleton 58 | @Requires(property = JCacheManager.JCACHE_ENABLED, value = "true") 59 | CacheManager cacheManager() { 60 | def cacheManager = Caching.getCachingProvider().cacheManager 61 | cacheManager.createCache('my-service', new MutableConfiguration()) 62 | return cacheManager 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/exceptions/CacheSystemException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.exceptions; 17 | 18 | /** 19 | * An exception that occurs when an internal cache system error occurs. 20 | * 21 | * @author Graeme Rocher 22 | * @since 1.0 23 | */ 24 | public class CacheSystemException extends RuntimeException { 25 | 26 | /** 27 | * Constructs a new CacheSystem exception with the specified detail message and 28 | * cause.

Note that the detail message associated with 29 | * {@code cause} is not automatically incorporated in 30 | * this exception's detail message. 31 | * 32 | * @param message The detail message (which is saved for later retrieval 33 | * by the {@link #getMessage()} method). 34 | * @param cause The cause (which is saved for later retrieval by the 35 | * {@link #getCause()} method). (A {@code null} value is 36 | * permitted, and indicates that the cause is nonexistent or 37 | * unknown.) 38 | */ 39 | public CacheSystemException(String message, Throwable cause) { 40 | super(message, cause); 41 | } 42 | 43 | /** 44 | * Constructs a new CacheSystem exception with the specified detail message.

Note that the detail message associated with 45 | * {@code cause} is not automatically incorporated in 46 | * this exception's detail message. 47 | * 48 | * @param message The detail message (which is saved for later retrieval 49 | * by the {@link #getMessage()} method). 50 | */ 51 | public CacheSystemException(String message) { 52 | super(message); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test-suite-caffeine-groovy/src/test/groovy/io/micronaut/cache/CaffeineTest.groovy: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.context.BeanContext 4 | import io.micronaut.context.annotation.Property 5 | import io.micronaut.http.client.BlockingHttpClient 6 | import io.micronaut.http.client.HttpClient 7 | import io.micronaut.http.client.annotation.Client 8 | import io.micronaut.test.extensions.spock.annotation.MicronautTest 9 | import jakarta.inject.Inject 10 | import spock.lang.Specification 11 | 12 | import java.util.List 13 | import java.util.stream.Stream 14 | 15 | @MicronautTest 16 | @Property(name = "micronaut.caches.counter.initial-capacity", value = "10") 17 | @Property(name = "micronaut.caches.counter.test-mode", value = "true") 18 | @Property(name = "micronaut.caches.counter.maximum-size", value = "20") 19 | @Property(name = "micronaut.caches.counter.listen-to-removals", value = "true") 20 | class CaffeineTest extends Specification { 21 | 22 | @Inject 23 | @Client("/") 24 | HttpClient httpClient 25 | 26 | @Inject 27 | BeanContext ctx 28 | 29 | def 'simple test'() { 30 | given: 31 | BlockingHttpClient client = httpClient.toBlocking() 32 | MyRemovalHandler bean = ctx.getBean(MyRemovalHandler.class) 33 | 34 | when: 35 | Integer inc = client.retrieve("/inc", Integer.class) 36 | 37 | then: 38 | inc == 1 39 | bean.removals.empty 40 | 41 | when: 42 | inc = client.retrieve("/inc", Integer.class) 43 | 44 | then: 45 | inc == 2 46 | bean.removals == ["test|1|REPLACED"] 47 | 48 | when: 49 | Integer get = client.retrieve("/get", Integer.class) 50 | 51 | then: 52 | get == 2 53 | 54 | when: 55 | client.exchange("/del") 56 | 57 | then: 58 | bean.removals == ["test|1|REPLACED", "test|2|EXPLICIT"] 59 | } 60 | 61 | def "conditional test"() { 62 | given: 63 | ConditionalService bean = ctx.getBean(ConditionalService) 64 | 65 | when: 66 | List first = (1..10).collect { bean.get(new ConditionalService.Id(it)) } 67 | List second = (1..10).collect { bean.get(new ConditionalService.Id(it)) } 68 | 69 | then: 70 | first.take(5) != second.take(5) 71 | first.drop(5) == second.drop(5) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/discovery/CachingCompositeDiscoveryClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.discovery; 17 | 18 | import io.micronaut.cache.annotation.Cacheable; 19 | import io.micronaut.context.annotation.Primary; 20 | import io.micronaut.context.annotation.Replaces; 21 | import io.micronaut.context.annotation.Requires; 22 | import io.micronaut.discovery.CompositeDiscoveryClient; 23 | import io.micronaut.discovery.DefaultCompositeDiscoveryClient; 24 | import io.micronaut.discovery.DiscoveryClient; 25 | import io.micronaut.discovery.ServiceInstance; 26 | import org.reactivestreams.Publisher; 27 | 28 | import java.util.List; 29 | 30 | /** 31 | * Replaces the default {@link io.micronaut.discovery.DefaultCompositeDiscoveryClient} with one that caches the return 32 | * values. 33 | * 34 | * @author Graeme Rocher 35 | * @since 1.0 36 | */ 37 | @Primary 38 | @Requires(beans = DiscoveryClientCacheConfiguration.class) 39 | @Replaces(DefaultCompositeDiscoveryClient.class) 40 | public class CachingCompositeDiscoveryClient extends CompositeDiscoveryClient { 41 | 42 | /** 43 | * @param discoveryClients The discovery clients 44 | */ 45 | public CachingCompositeDiscoveryClient(DiscoveryClient[] discoveryClients) { 46 | super(discoveryClients); 47 | } 48 | 49 | @Override 50 | @Cacheable(DiscoveryClientCacheConfiguration.CACHE_NAME) 51 | public Publisher> getInstances(String serviceId) { 52 | return super.getInstances(serviceId); 53 | } 54 | 55 | @Override 56 | @Cacheable(DiscoveryClientCacheConfiguration.CACHE_NAME) 57 | public Publisher> getServiceIds() { 58 | return super.getServiceIds(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cache-core/src/main/java/io/micronaut/cache/interceptor/ParametersKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.interceptor; 17 | 18 | import io.micronaut.core.util.ArrayUtils; 19 | 20 | import java.io.Serializable; 21 | import java.util.Arrays; 22 | 23 | /** 24 | * A key that uses the parameters of a method. 25 | * 26 | * @author Graeme Rocher 27 | * @since 1.0 28 | */ 29 | public class ParametersKey implements Serializable { 30 | 31 | public static final ParametersKey ZERO_ARG_KEY = new ParametersKey(); 32 | public static final int EMPTY_OBJECT_ARRAY_HASH_CODE = Arrays.hashCode(ArrayUtils.EMPTY_OBJECT_ARRAY); 33 | 34 | private final Object[] params; 35 | private final int hashCode; 36 | 37 | /** 38 | * @param params Parameters of the method 39 | */ 40 | public ParametersKey(Object... params) { 41 | if (ArrayUtils.isEmpty(params)) { 42 | this.params = ArrayUtils.EMPTY_OBJECT_ARRAY; 43 | this.hashCode = EMPTY_OBJECT_ARRAY_HASH_CODE; 44 | } else { 45 | this.params = new Object[params.length]; 46 | System.arraycopy(params, 0, this.params, 0, params.length); 47 | this.hashCode = Arrays.deepHashCode(this.params); 48 | } 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) { 54 | return true; 55 | } 56 | return o instanceof ParametersKey && Arrays.deepEquals(params, ((ParametersKey) o).params); 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return hashCode; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return ParametersKey.class.getSimpleName() + ": " + ArrayUtils.toString(params); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cache-noop/src/main/java/io/micronaut/cache/noop/NoOpCacheManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.noop; 17 | 18 | import io.micronaut.cache.DefaultCacheManager; 19 | import io.micronaut.cache.SyncCache; 20 | import io.micronaut.context.annotation.Primary; 21 | import io.micronaut.context.annotation.Replaces; 22 | import io.micronaut.context.annotation.Requires; 23 | import org.jspecify.annotations.NonNull; 24 | import io.micronaut.core.util.StringUtils; 25 | 26 | import java.util.Map; 27 | import java.util.Set; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | 30 | /** 31 | * A no operation {@link io.micronaut.cache.CacheManager} implementation suitable for disabling caching. 32 | * 33 | *

Will simply accept any items into the cache without actually storing them. 34 | * 35 | * @author Marcel Overdijk 36 | * @since 1.0.0 37 | */ 38 | @Replaces(DefaultCacheManager.class) 39 | @Requires(property = "noop-cache.enabled", value = StringUtils.TRUE) 40 | @Primary 41 | public class NoOpCacheManager implements io.micronaut.cache.CacheManager { 42 | 43 | private Map cacheMap; 44 | 45 | /** 46 | * Constructor. 47 | */ 48 | public NoOpCacheManager() { 49 | this.cacheMap = new ConcurrentHashMap<>(); 50 | } 51 | 52 | @NonNull 53 | @Override 54 | public Set getCacheNames() { 55 | return cacheMap.keySet(); 56 | } 57 | 58 | @NonNull 59 | @Override 60 | public SyncCache getCache(String name) { 61 | NoOpSyncCache syncCache = this.cacheMap.get(name); 62 | if (syncCache == null) { 63 | syncCache = new NoOpSyncCache(name); 64 | this.cacheMap.put(name, syncCache); 65 | } 66 | return syncCache; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /cache-infinispan/src/test/groovy/io/micronaut/cache/infinispan/InfinispanAsyncCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.infinispan 17 | 18 | import io.micronaut.cache.tck.AbstractAsyncCacheSpec 19 | import io.micronaut.context.ApplicationContext 20 | import org.testcontainers.containers.GenericContainer 21 | import org.testcontainers.containers.wait.strategy.Wait 22 | import org.testcontainers.spock.Testcontainers 23 | import spock.lang.Retry 24 | import spock.lang.Shared 25 | 26 | @Testcontainers 27 | @Retry 28 | class InfinispanAsyncCacheSpec extends AbstractAsyncCacheSpec { 29 | 30 | @Shared 31 | GenericContainer infinispan = new GenericContainer("infinispan/server:${System.getProperty('infinispanVersion')}") 32 | .withExposedPorts(11222) 33 | .withEnv('USER', 'user') 34 | .withEnv('PASS', 'pass') 35 | .waitingFor(Wait.forHttp("/rest/v2/cache-managers/default/health/status")) 36 | 37 | @Override 38 | ApplicationContext createApplicationContext() { 39 | return ApplicationContext.run([ 40 | "infinispan.client.hotrod.force-return-values": true, 41 | "infinispan.client.hotrod.server.host": "localhost", 42 | "infinispan.client.hotrod.server.port": infinispan.firstMappedPort, 43 | "infinispan.client.hotrod.security.authentication.username": "user", 44 | "infinispan.client.hotrod.security.authentication.password": "pass", 45 | "infinispan.client.hotrod.security.authentication.realm": "default", 46 | "infinispan.client.hotrod.security.authentication.server-name": "infinispan", 47 | "infinispan.client.hotrod.security.authentication.sasl-mechanism": "DIGEST-MD5" 48 | ]) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cache-noop/src/test/groovy/io/micronaut/cache/noop/NoOpCacheConfigurationSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.noop 17 | 18 | import io.micronaut.cache.CacheManager 19 | import io.micronaut.context.ApplicationContext 20 | import spock.lang.Specification 21 | 22 | /** 23 | * @author Marcel Overdijk 24 | * @since 1.0.0 25 | */ 26 | class NoOpCacheConfigurationSpec extends Specification { 27 | 28 | void "test no operation cache manager is not created when noop-cache.enabled property is not defined"() { 29 | setup: 30 | ApplicationContext ctx = ApplicationContext.run(ApplicationContext) 31 | 32 | when: 33 | CacheManager cacheManager = ctx.getBean(CacheManager) 34 | 35 | then: 36 | !(cacheManager instanceof NoOpCacheManager) 37 | 38 | cleanup: 39 | ctx.close() 40 | } 41 | 42 | void "test no operation cache manager is not created when noop-cache.enabled = false"() { 43 | setup: 44 | ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [ 45 | "noop-cache.enabled": false 46 | ]) 47 | 48 | when: 49 | CacheManager cacheManager = ctx.getBean(CacheManager) 50 | 51 | then: 52 | !(cacheManager instanceof NoOpCacheManager) 53 | 54 | cleanup: 55 | ctx.close() 56 | } 57 | 58 | void "test no operation cache manager is created when noop-cache.enabled = true"() { 59 | setup: 60 | ApplicationContext ctx = ApplicationContext.run(ApplicationContext, [ 61 | "noop-cache.enabled": true 62 | ]) 63 | 64 | when: 65 | CacheManager cacheManager = ctx.getBean(CacheManager) 66 | 67 | then: 68 | cacheManager instanceof NoOpCacheManager 69 | 70 | cleanup: 71 | ctx.close() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cache-infinispan/src/test/groovy/io/micronaut/cache/infinispan/InfinispanSyncCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.infinispan 17 | 18 | import io.micronaut.cache.tck.AbstractSyncCacheSpec 19 | import io.micronaut.context.ApplicationContext 20 | import org.testcontainers.containers.GenericContainer 21 | import org.testcontainers.containers.wait.strategy.Wait 22 | import org.testcontainers.containers.wait.strategy.WaitStrategy 23 | import org.testcontainers.spock.Testcontainers 24 | import spock.lang.Retry 25 | import spock.lang.Shared 26 | 27 | @Testcontainers 28 | @Retry 29 | class InfinispanSyncCacheSpec extends AbstractSyncCacheSpec { 30 | 31 | @Shared 32 | GenericContainer infinispan = new GenericContainer("infinispan/server:${System.getProperty('infinispanVersion')}") 33 | .withExposedPorts(11222) 34 | .withEnv('USER', 'user') 35 | .withEnv('PASS', 'pass') 36 | .waitingFor(Wait.forHttp("/rest/v2/cache-managers/default/health/status")) 37 | 38 | 39 | @Override 40 | ApplicationContext createApplicationContext() { 41 | return ApplicationContext.run([ 42 | "infinispan.client.hotrod.force-return-values": true, 43 | "infinispan.client.hotrod.server.host": "localhost", 44 | "infinispan.client.hotrod.server.port": infinispan.firstMappedPort, 45 | "infinispan.client.hotrod.security.authentication.username": "user", 46 | "infinispan.client.hotrod.security.authentication.password": "pass", 47 | "infinispan.client.hotrod.security.authentication.realm": "default", 48 | "infinispan.client.hotrod.security.authentication.server-name": "infinispan", 49 | "infinispan.client.hotrod.security.authentication.sasl-mechanism": "DIGEST-MD5" 50 | ]) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | micronaut = "5.0.0-M2" 3 | 4 | kotlin = "2.2.21" 5 | 6 | cache-ri-impl = "1.1.1" 7 | managed-caffeine = "3.2.3" 8 | managed-ehcache = "3.11.1" 9 | managed-hazelcast = "5.6.0" 10 | managed-infinispan = "16.0.3" 11 | managed-jcache = "1.1.1" 12 | 13 | micronaut-micrometer = "5.13.1" 14 | micronaut-serde = "3.0.0-M1" 15 | micronaut-test-resources = "2.10.1" 16 | micronaut-validation = "4.12.0" 17 | graal-svm = "23.1.9" 18 | graal-plugin = "0.11.3" 19 | 20 | [libraries] 21 | # Core 22 | micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' } 23 | 24 | # Managed 25 | managed-caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "managed-caffeine" } 26 | managed-hazelcast = { module = "com.hazelcast:hazelcast", version.ref = "managed-hazelcast" } 27 | managed-ehcache-clustered = { module = "org.ehcache:ehcache-clustered", version.ref = "managed-ehcache" } 28 | managed-ehcache = { module = "org.ehcache:ehcache", version.ref = "managed-ehcache" } 29 | managed-infinispan-client-hotrod = { module = "org.infinispan:infinispan-client-hotrod", version.ref = "managed-infinispan" } 30 | managed-infinispan-core = { module = "org.infinispan:infinispan-core", version.ref = "managed-infinispan" } 31 | managed-jcache = { module = "javax.cache:cache-api", version.ref = "managed-jcache" } 32 | 33 | micronaut-micrometer = { module = "io.micronaut.micrometer:micronaut-micrometer-bom", version.ref = "micronaut-micrometer" } 34 | micronaut-serde = { module = "io.micronaut.serde:micronaut-serde-bom", version.ref = "micronaut-serde" } 35 | micronaut-test-resources = { module = "io.micronaut.testresources:micronaut-test-resources-bom", version.ref = "micronaut-test-resources" } 36 | micronaut-validation = { module = "io.micronaut.validation:micronaut-validation-bom", version.ref = "micronaut-validation" } 37 | 38 | cache-api = { module = "javax.cache:cache-api", version.ref = "cache-ri-impl" } 39 | cache-ri-impl = { module = "org.jsr107.ri:cache-ri-impl", version.ref = "cache-ri-impl" } 40 | testcontainers-junit = { module = "org.testcontainers:junit-jupiter" } 41 | testcontainers-spock = { module = "org.testcontainers:spock" } 42 | graal-svm = { module = "org.graalvm.nativeimage:svm", version.ref = "graal-svm" } 43 | gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 44 | gradle-graal = { module = "org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin", version.ref = "graal-plugin" } 45 | -------------------------------------------------------------------------------- /cache-noop/src/main/java/io/micronaut/cache/noop/NoOpSyncCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.noop; 17 | 18 | import io.micronaut.cache.SyncCache; 19 | import org.jspecify.annotations.NonNull; 20 | import io.micronaut.core.type.Argument; 21 | 22 | import java.util.Optional; 23 | import java.util.function.Supplier; 24 | 25 | /** 26 | * A no operation {@link SyncCache} implementation suitable for disabling caching. 27 | * 28 | * @author Marcel Overdijk 29 | * @since 1.0.0 30 | */ 31 | public class NoOpSyncCache implements SyncCache { 32 | 33 | private String name; 34 | 35 | /** 36 | * Constructor. 37 | * 38 | * @param name the cache name 39 | */ 40 | public NoOpSyncCache(String name) { 41 | this.name = name; 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public Optional get(@NonNull Object key, @NonNull Argument requiredType) { 47 | return Optional.empty(); 48 | } 49 | 50 | @Override 51 | public T get(@NonNull Object key, @NonNull Argument requiredType, @NonNull Supplier supplier) { 52 | return supplier.get(); 53 | } 54 | 55 | @SuppressWarnings("unchecked") 56 | @NonNull 57 | @Override 58 | public Optional putIfAbsent(@NonNull Object key, @NonNull T value) { 59 | return Optional.of(value); 60 | } 61 | 62 | @SuppressWarnings("unchecked") 63 | @Override 64 | public void put(@NonNull Object key, @NonNull Object value) { 65 | } 66 | 67 | @SuppressWarnings("unchecked") 68 | @Override 69 | public void invalidate(@NonNull Object key) { 70 | } 71 | 72 | @Override 73 | public void invalidateAll() { 74 | } 75 | 76 | @Override 77 | public String getName() { 78 | return name; 79 | } 80 | 81 | @Override 82 | public Object getNativeCache() { 83 | return this; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /cache-hazelcast/src/main/java/io/micronaut/cache/hazelcast/HazelcastSyncCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.hazelcast; 17 | 18 | import com.hazelcast.map.IMap; 19 | import io.micronaut.cache.AbstractMapBasedSyncCache; 20 | import io.micronaut.cache.AsyncCache; 21 | import org.jspecify.annotations.NonNull; 22 | import io.micronaut.core.convert.ConversionService; 23 | import io.micronaut.core.util.ArgumentUtils; 24 | 25 | import java.util.concurrent.ExecutorService; 26 | 27 | /** 28 | * A {@link io.micronaut.cache.SyncCache} implementation based on Hazelcast. 29 | * 30 | * @author Nirav Assar 31 | * @since 1.0.0 32 | */ 33 | public class HazelcastSyncCache extends AbstractMapBasedSyncCache> { 34 | 35 | private final ExecutorService executorService; 36 | 37 | /** 38 | * @param conversionService the conversion service 39 | * @param nativeCache the native cache 40 | * @param executorService managers the pool of executors 41 | */ 42 | public HazelcastSyncCache(ConversionService conversionService, 43 | IMap nativeCache, 44 | ExecutorService executorService) { 45 | super(conversionService, nativeCache); 46 | this.executorService = executorService; 47 | } 48 | 49 | @SuppressWarnings("unchecked") 50 | @Override 51 | public void put(@NonNull Object key, @NonNull Object value) { 52 | ArgumentUtils.requireNonNull("key", key); 53 | ArgumentUtils.requireNonNull("value", value); 54 | getNativeCache().set(key, value); 55 | } 56 | 57 | @Override 58 | public String getName() { 59 | return getNativeCache().getName(); 60 | } 61 | 62 | @Override 63 | public AsyncCache> async() { 64 | return new HazelcastAsyncCache(getConversionService(), getNativeCache(), executorService); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test-suite-caffeine-kotlin/src/test/kotlin/io/micronaut/cache/CaffeineTest.kt: -------------------------------------------------------------------------------- 1 | package io.micronaut.cache 2 | 3 | import io.micronaut.context.BeanContext 4 | import io.micronaut.context.annotation.Property 5 | import io.micronaut.http.client.HttpClient 6 | import io.micronaut.http.client.annotation.Client 7 | import io.micronaut.test.extensions.junit5.annotation.MicronautTest 8 | import jakarta.inject.Inject 9 | import org.junit.jupiter.api.Assertions 10 | import org.junit.jupiter.api.Assertions.assertEquals 11 | import org.junit.jupiter.api.Assertions.assertTrue 12 | import org.junit.jupiter.api.Test 13 | 14 | @MicronautTest 15 | @Property(name = "micronaut.caches.counter.initial-capacity", value = "10") 16 | @Property(name = "micronaut.caches.counter.test-mode", value = "true") 17 | @Property(name = "micronaut.caches.counter.maximum-size", value = "20") 18 | @Property(name = "micronaut.caches.counter.listen-to-removals", value = "true") 19 | internal class CaffeineTest { 20 | 21 | @Inject 22 | @field:Client("/") 23 | lateinit var httpClient: HttpClient 24 | 25 | @Inject 26 | lateinit var ctx: BeanContext 27 | 28 | @Test 29 | fun simpleTest() { 30 | val client = httpClient.toBlocking() 31 | val bean = ctx.getBean(MyRemovalHandler::class.java) 32 | 33 | var inc = client.retrieve("/inc", Int::class.java) 34 | assertEquals(1, inc) 35 | assertTrue { bean.removals.isEmpty() } 36 | 37 | inc = client.retrieve("/inc", Int::class.java) 38 | assertEquals(2, inc) 39 | val expectedEventsAfterReplacement = listOf("test|1|REPLACED") 40 | assertEquals(expectedEventsAfterReplacement, bean.removals) 41 | 42 | val get = client.retrieve("/get", Int::class.java) 43 | assertEquals(2, get) 44 | 45 | client.exchange("/del") 46 | val expectedEventsAfterRemoval = listOf("test|1|REPLACED", "test|2|EXPLICIT") 47 | assertEquals(expectedEventsAfterRemoval, bean.removals) 48 | } 49 | 50 | @Test 51 | fun conditionalTest() { 52 | val bean = ctx.getBean(ConditionalService::class.java) 53 | 54 | // Get the same thing twice, ids > 5 are cached 55 | val first = generateSequence(1) { it + 1 }.take(10).map { bean.get(ConditionalService.Id(it)) }.toList() 56 | val second = generateSequence(1) { it + 1 }.take(10).map { bean.get(ConditionalService.Id(it)) }.toList() 57 | 58 | Assertions.assertNotEquals(first.subList(0, 5), second.subList(0, 5)) 59 | assertEquals(first.subList(5, 10), second.subList(5, 10)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/docs/guide/hazelcast.adoc: -------------------------------------------------------------------------------- 1 | https://hazelcast.org/[Hazelcast] caching is supported. Micronaut will create a Hazelcast client instance to connect to 2 | an existing Hazelcast server cluster or create an standalone embedded Hazelcast member instance. 3 | 4 | NOTE: This module is built and tested with Hazelcast {hazelcastVersion} 5 | 6 | Add the Micronaut Hazelcast module as a dependency: 7 | 8 | dependency:io.micronaut.cache:micronaut-cache-hazelcast[] 9 | 10 | You can also add Hazelcast module to your project using cli feature as below: 11 | 12 | [source,bash] 13 | .Create a Micronaut application with Hazelcast module 14 | ---- 15 | $ mn create-app hello-world -f hazelcast 16 | ---- 17 | 18 | The minimal configuration to use Hazelcast is to simply declare `hazelcast:` with a network configuration for addresses of the 19 | Hazelcast cluster (example below). 20 | 21 | [configuration] 22 | ---- 23 | hazelcast: 24 | client: 25 | network: 26 | addresses: ['121.0.0.1:5701'] 27 | ---- 28 | 29 | If you provide a Hazelcast configuration file (ex.: `hazelcast.xml`, `hazelcast.yml`, `hazelcast-client.xml`, or `hazelcast-client.yml`) in the working directory or classpath, Micronaut will use this configuration file to configure Hazelcast instance. 30 | 31 | When using the link:{api}/io/micronaut/cache/annotation/Cacheable.html[@Cacheable] and other Cache Annotations, Micronaut will create 32 | the Hazelcast client and use the underlying https://docs.hazelcast.org/docs/latest/javadoc/com/hazelcast/core/IMap.html[IMap] Cache Datastore 33 | on the server. 34 | 35 | The full list of configurable options is below. 36 | 37 | include::{includedir}configurationProperties/io.micronaut.cache.hazelcast.HazelcastClientConfiguration.adoc[] 38 | 39 | For settings not in the above list, a https://docs.micronaut.io/latest/api/io/micronaut/context/event/BeanCreatedEventListener.html[BeanCreatedEventListener] can be registered for 40 | link:{api}/io/micronaut/cache/hazelcast/HazelcastClientConfiguration.html[HazelcastClientConfiguration] or link:{api}/io/micronaut/cache/hazelcast/HazelcastMemberConfiguration.html[HazelcastMemberConfiguration]. The listener will allow all properties to be set directly on the configuration instance. 41 | 42 | snippet::io.micronaut.cache.HazelcastAdditionalSettings[project-base="test-suite-hazelcast",tags="clazz"] 43 | 44 | Alternatively, the `HazelcastClientConfiguration` or `HazelcastMemberConfiguration` bean may be replaced with your own implementation. 45 | 46 | To disable Hazelcast: 47 | 48 | [configuration] 49 | ---- 50 | hazelcast: 51 | enabled: false 52 | ---- 53 | -------------------------------------------------------------------------------- /cache-infinispan/src/main/java/io/micronaut/cache/infinispan/InfinispanCacheManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache.infinispan; 17 | 18 | import io.micronaut.cache.DynamicCacheManager; 19 | import io.micronaut.cache.SyncCache; 20 | import org.jspecify.annotations.NonNull; 21 | import io.micronaut.core.convert.ConversionService; 22 | import jakarta.inject.Singleton; 23 | import org.infinispan.client.hotrod.RemoteCache; 24 | import org.infinispan.client.hotrod.RemoteCacheManager; 25 | import org.infinispan.commons.configuration.BasicConfiguration; 26 | import org.infinispan.configuration.cache.ConfigurationBuilder; 27 | 28 | /** 29 | * A {@link DynamicCacheManager} that creates Infinispan caches on demand. 30 | * 31 | * @author Álvaro Sánchez-Mariscal 32 | * @since 1.0.0 33 | * @see org.infinispan.client.hotrod.RemoteCacheManagerAdmin#getOrCreateCache(String, BasicConfiguration) 34 | */ 35 | @Singleton 36 | public class InfinispanCacheManager implements DynamicCacheManager> { 37 | 38 | private final RemoteCacheManager remoteCacheManager; 39 | private final ConversionService conversionService; 40 | 41 | /** 42 | * @param remoteCacheManager the Infinispan remote cache manager 43 | * @param conversionService the conversion service 44 | */ 45 | public InfinispanCacheManager(RemoteCacheManager remoteCacheManager, ConversionService conversionService) { 46 | this.remoteCacheManager = remoteCacheManager; 47 | this.conversionService = conversionService; 48 | } 49 | 50 | @NonNull 51 | @Override 52 | public SyncCache> getCache(String name) { 53 | BasicConfiguration basicConfiguration = new ConfigurationBuilder().build(); 54 | RemoteCache nativeCache = remoteCacheManager.administration().getOrCreateCache(name, basicConfiguration); 55 | return new InfinispanSyncCache(conversionService, nativeCache); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cache-caffeine/src/test/groovy/io/micronaut/cache/DynamicCacheSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017-2020 original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.micronaut.cache 17 | 18 | import io.micronaut.context.ApplicationContext 19 | import io.micronaut.context.annotation.Requires 20 | import jakarta.inject.Singleton 21 | import spock.lang.Specification 22 | 23 | class DynamicCacheSpec extends Specification { 24 | 25 | void "test behavior in the presence of a dynamic cache manager"() { 26 | given: 27 | ApplicationContext applicationContext = ApplicationContext.run( 28 | 'spec.name': DynamicCacheSpec.simpleName, 29 | 'micronaut.caches.counter.initialCapacity':10, 30 | 'micronaut.caches.counter.testMode':true, 31 | 'micronaut.caches.counter.maximumSize':20, 32 | 'micronaut.caches.counter2.initialCapacity':10, 33 | 'micronaut.caches.counter2.maximumSize':20, 34 | 'micronaut.caches.counter2.testMode':true 35 | ) 36 | CacheManager cacheManager = applicationContext.getBean(CacheManager) 37 | 38 | expect: 39 | cacheManager.cacheNames == ["counter", "counter2"] as Set 40 | 41 | when: 42 | SyncCache cache = cacheManager.getCache('counter') 43 | 44 | then: 45 | noExceptionThrown() 46 | cache != null 47 | 48 | when: 49 | cache = cacheManager.getCache("fooBar") 50 | 51 | then: 52 | noExceptionThrown() 53 | cache instanceof DynamicCache 54 | cacheManager.cacheNames == ["counter", "counter2", "fooBar"] as Set 55 | 56 | cleanup: 57 | applicationContext.close() 58 | } 59 | 60 | @Requires(property = "spec.name", value = "DynamicCacheSpec") 61 | @Singleton 62 | static class MyDynamicCacheManager implements DynamicCacheManager { 63 | 64 | @Override 65 | SyncCache getCache(String name) { 66 | return new DynamicCache() 67 | } 68 | } 69 | 70 | } 71 | --------------------------------------------------------------------------------