├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── maven.yml │ └── pmd.yml ├── .gitignore ├── LICENSE ├── README.adoc ├── bucket4j-spring-boot-starter-context ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── giffing │ └── bucket4j │ └── spring │ └── boot │ └── starter │ └── context │ ├── Bucket4jConfigurationHolder.java │ ├── Condition.java │ ├── ExecutePredicate.java │ ├── ExecutePredicateDefinition.java │ ├── ExpressionParams.java │ ├── FilterMethod.java │ ├── IgnoreRateLimiting.java │ ├── KeyFilter.java │ ├── PostRateLimitCheck.java │ ├── RateLimitCheck.java │ ├── RateLimitConditionMatchingStrategy.java │ ├── RateLimitException.java │ ├── RateLimitResult.java │ ├── RateLimitResultWrapper.java │ ├── RateLimiting.java │ ├── RefillSpeed.java │ ├── constraintvalidations │ ├── Bucket4JConfigurationPredicateNameValidator.java │ ├── DurationChronoUnitValidator.java │ ├── RateLimitBandWidthIdsValidator.java │ ├── ValidBandWidthIds.java │ ├── ValidDurationChronoUnit.java │ └── ValidPredicateNames.java │ ├── metrics │ ├── MetricBucketListener.java │ ├── MetricHandler.java │ ├── MetricTagResult.java │ └── MetricType.java │ ├── properties │ ├── BandWidth.java │ ├── Bucket4JBootProperties.java │ ├── Bucket4JConfiguration.java │ ├── FilterConfiguration.java │ ├── MethodProperties.java │ ├── MetricTag.java │ ├── Metrics.java │ └── RateLimit.java │ └── qualifier │ ├── Gateway.java │ ├── Servlet.java │ └── Webflux.java ├── bucket4j-spring-boot-starter ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ ├── Bucket4jStartupCheckConfiguration.java │ │ │ ├── config │ │ │ ├── aspect │ │ │ │ ├── Bucket4jAopConfig.java │ │ │ │ └── RateLimitAspect.java │ │ │ ├── cache │ │ │ │ ├── AbstractCacheResolverTemplate.java │ │ │ │ ├── AsyncCacheResolver.java │ │ │ │ ├── Bucket4jCacheConfiguration.java │ │ │ │ ├── CacheManager.java │ │ │ │ ├── CacheResolver.java │ │ │ │ ├── CacheUpdateEvent.java │ │ │ │ ├── CacheUpdateListener.java │ │ │ │ ├── ProxyManagerWrapper.java │ │ │ │ ├── SyncCacheResolver.java │ │ │ │ ├── hazelcast │ │ │ │ │ ├── HazelcastCacheListener.java │ │ │ │ │ ├── HazelcastCacheManager.java │ │ │ │ │ ├── HazelcastCacheResolver.java │ │ │ │ │ ├── HazelcastReactiveBucket4jCacheConfiguration.java │ │ │ │ │ └── HazelcastSpringBucket4jCacheConfiguration.java │ │ │ │ ├── ignite │ │ │ │ │ ├── IgniteBucket4jCacheConfiguration.java │ │ │ │ │ ├── IgniteCacheListener.java │ │ │ │ │ ├── IgniteCacheManager.java │ │ │ │ │ └── IgniteCacheResolver.java │ │ │ │ ├── infinispan │ │ │ │ │ ├── InfinispanBucket4jCacheConfiguration.java │ │ │ │ │ ├── InfinispanCacheListener.java │ │ │ │ │ ├── InfinispanCacheManager.java │ │ │ │ │ └── InfinispanCacheResolver.java │ │ │ │ ├── jcache │ │ │ │ │ ├── InfinispanJCacheBucket4jConfiguration.java │ │ │ │ │ ├── InfinispanJCacheCacheResolver.java │ │ │ │ │ ├── JCacheBucket4jConfiguration.java │ │ │ │ │ ├── JCacheCacheListener.java │ │ │ │ │ ├── JCacheCacheManager.java │ │ │ │ │ └── JCacheCacheResolver.java │ │ │ │ └── redis │ │ │ │ │ ├── jedis │ │ │ │ │ ├── JedisBucket4jConfiguration.java │ │ │ │ │ ├── JedisCacheListener.java │ │ │ │ │ ├── JedisCacheManager.java │ │ │ │ │ └── JedisCacheResolver.java │ │ │ │ │ ├── lettuce │ │ │ │ │ ├── LettuceBucket4jConfiguration.java │ │ │ │ │ ├── LettuceCacheListener.java │ │ │ │ │ ├── LettuceCacheManager.java │ │ │ │ │ └── LettuceCacheResolver.java │ │ │ │ │ └── redisson │ │ │ │ │ ├── RedissonBucket4jConfiguration.java │ │ │ │ │ ├── RedissonCacheListener.java │ │ │ │ │ ├── RedissonCacheManager.java │ │ │ │ │ └── RedissonCacheResolver.java │ │ │ ├── condition │ │ │ │ ├── ConditionalOnAsynchronousPropertyCondition.java │ │ │ │ ├── ConditionalOnBucket4jEnabled.java │ │ │ │ ├── ConditionalOnCache.java │ │ │ │ ├── ConditionalOnFilterConfigCacheEnabled.java │ │ │ │ ├── ConditionalOnSynchronousPropertyCondition.java │ │ │ │ ├── OnAsynchronousPropertyCondition.java │ │ │ │ └── OnSynchronousPropertyCondition.java │ │ │ ├── failureanalyzer │ │ │ │ └── Bucket4JAutoConfigFailureAnalyzer.java │ │ │ ├── filter │ │ │ │ ├── Bucket4JBaseConfiguration.java │ │ │ │ ├── predicate │ │ │ │ │ ├── HeaderExecutePredicate.java │ │ │ │ │ ├── MethodExecutePredicate.java │ │ │ │ │ ├── PathExecutePredicate.java │ │ │ │ │ └── QueryExecutePredicate.java │ │ │ │ ├── reactive │ │ │ │ │ ├── gateway │ │ │ │ │ │ ├── Bucket4JAutoConfigurationSpringCloudGatewayFilter.java │ │ │ │ │ │ └── Bucket4JAutoConfigurationSpringCloudGatewayFilterBeans.java │ │ │ │ │ ├── predicate │ │ │ │ │ │ ├── WebfluxExecutePredicateConfiguration.java │ │ │ │ │ │ ├── WebfluxHeaderExecutePredicate.java │ │ │ │ │ │ ├── WebfluxMethodPredicate.java │ │ │ │ │ │ ├── WebfluxPathExecutePredicate.java │ │ │ │ │ │ └── WebfluxQueryExecutePredicate.java │ │ │ │ │ └── webflux │ │ │ │ │ │ ├── Bucket4JAutoConfigurationWebfluxFilter.java │ │ │ │ │ │ └── Bucket4JAutoConfigurationWebfluxFilterBeans.java │ │ │ │ └── servlet │ │ │ │ │ ├── Bucket4JAutoConfigurationServletFilter.java │ │ │ │ │ ├── Bucket4JAutoConfigurationServletFilterBeans.java │ │ │ │ │ └── predicate │ │ │ │ │ ├── ServletHeaderExecutePredicate.java │ │ │ │ │ ├── ServletMethodPredicate.java │ │ │ │ │ ├── ServletPathExecutePredicate.java │ │ │ │ │ ├── ServletQueryExecutePredicate.java │ │ │ │ │ └── ServletRequestExecutePredicateConfiguration.java │ │ │ ├── metrics │ │ │ │ └── actuator │ │ │ │ │ ├── Bucket4jEndpoint.java │ │ │ │ │ ├── Bucket4jMetricHandler.java │ │ │ │ │ ├── Bucket4jMetricsConfiguration.java │ │ │ │ │ └── SpringBootActuatorConfig.java │ │ │ └── service │ │ │ │ └── ServiceConfiguration.java │ │ │ ├── exception │ │ │ ├── Bucket4jGeneralException.java │ │ │ ├── ExecutePredicateInstantiationException.java │ │ │ ├── JCacheNotFoundException.java │ │ │ ├── NoCacheConfiguredException.java │ │ │ ├── RateLimitUnknownParameterException.java │ │ │ ├── RateLimitingFallbackMethodNotFoundException.java │ │ │ ├── RateLimitingFallbackMethodParameterMismatchException.java │ │ │ ├── RateLimitingFallbackReturnTypesMismatchException.java │ │ │ ├── RateLimitingMethodNameNotConfiguredException.java │ │ │ └── RateLimitingMultipleFallbackMethodsFoundException.java │ │ │ ├── filter │ │ │ ├── reactive │ │ │ │ ├── AbstractReactiveFilter.java │ │ │ │ ├── ReactiveFilterChain.java │ │ │ │ ├── ReactiveRateLimitException.java │ │ │ │ ├── gateway │ │ │ │ │ └── SpringCloudGatewayRateLimitFilter.java │ │ │ │ └── webflux │ │ │ │ │ └── WebfluxWebFilter.java │ │ │ └── servlet │ │ │ │ └── ServletRequestFilter.java │ │ │ ├── service │ │ │ ├── ExpressionService.java │ │ │ └── RateLimitService.java │ │ │ └── utils │ │ │ ├── Bucket4JUtils.java │ │ │ └── RateLimitAopUtils.java │ └── resources │ │ └── META-INF │ │ ├── native-image │ │ ├── jni-config.json │ │ ├── proxy-config.json │ │ └── reflect-config.json │ │ ├── spring.factories │ │ └── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ └── test │ └── java │ └── com │ └── giffing │ └── bucket4j │ └── spring │ └── boot │ └── starter │ ├── config │ └── filter │ │ ├── Bucket4JConfigurationTest.java │ │ ├── RateLimitBandWidthIdTest.java │ │ └── predicate │ │ ├── ConfigPredicateNameValidatorTest.java │ │ ├── HeaderExecutePredicateTest.java │ │ ├── MethodExecutePredicateTest.java │ │ └── QueryExecutePredicateTest.java │ ├── gateway │ └── SpringCloudGatewayRateLimitFilterTest.java │ ├── servlet │ └── ServletRateLimitFilterTest.java │ └── webflux │ └── WebfluxRateLimitFilterTest.java ├── examples ├── caffeine │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ └── examples │ │ │ │ └── caffeine │ │ │ │ ├── CaffeineApplication.java │ │ │ │ ├── CustomQueryExecutePredicate.java │ │ │ │ ├── DebugMetricHandler.java │ │ │ │ ├── RateLimitExceptionHandler.java │ │ │ │ ├── SimpleSecurityFilter.java │ │ │ │ ├── TestController.java │ │ │ │ └── TestService.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── examples │ │ │ └── caffeine │ │ │ └── CaffeineGeneralSuiteTest.java │ │ └── resources │ │ └── application.yml ├── ehcache │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ └── examples │ │ │ │ └── ehcache │ │ │ │ ├── EhcacheApplication.java │ │ │ │ └── controller │ │ │ │ ├── DebugMetricHandler.java │ │ │ │ └── TestController.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── ehcache.xml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── examples │ │ │ └── ehcache │ │ │ └── EhcacheGeneralSuiteTest.java │ │ └── resources │ │ └── application.yml ├── gateway │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ └── examples │ │ │ │ └── gateway │ │ │ │ ├── GatewayMetricHandler.java │ │ │ │ ├── GatewaySampleApplication.java │ │ │ │ ├── TestController.java │ │ │ │ └── UriConfiguration.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── hazelcast.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── giffing │ │ └── bucket4j │ │ └── spring │ │ └── boot │ │ └── starter │ │ └── examples │ │ └── gateway │ │ └── GatewaySampleApplicationTest.java ├── general-tests │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── giffing │ │ └── bucket4j │ │ └── spring │ │ └── boot │ │ └── starter │ │ └── general │ │ └── tests │ │ ├── filter │ │ ├── reactive │ │ │ ├── ReactiveGreadyRefillSpeedTest.java │ │ │ ├── ReactiveIntervalRefillSpeedTest.java │ │ │ ├── ReactiveRateLimitTest.java │ │ │ ├── ReactiveTestApplication.java │ │ │ ├── WebfluxTestSuite.java │ │ │ └── controller │ │ │ │ └── ReactiveController.java │ │ └── servlet │ │ │ ├── AddResponseHeaderTest.java │ │ │ ├── Bucket4jDisabledTest.java │ │ │ ├── ChangeResponseHttpStatusCodeTest.java │ │ │ ├── EmptyHttpResponseTest.java │ │ │ ├── ExecuteConditionTest.java │ │ │ ├── GreadyRefillSpeedTest.java │ │ │ ├── IntervalRefillSpeedTest.java │ │ │ ├── MockMvcHelper.java │ │ │ ├── PostExecuteConditionTest.java │ │ │ ├── ServletRateLimitTest.java │ │ │ ├── ServletTestApplication.java │ │ │ ├── ServletTestSuite.java │ │ │ ├── SkipConditionTest.java │ │ │ ├── controller │ │ │ └── ServletController.java │ │ │ └── security │ │ │ └── SimpleSecurityFilter.java │ │ └── method │ │ ├── failures │ │ └── RateLimitConfigurationStartupFailuresTest.java │ │ └── method │ │ ├── Bucket4jDisabledTest.java │ │ ├── ClassLevelTestService.java │ │ ├── IgnoreOnClassLevelTestService.java │ │ ├── MethodRateLimitTest.java │ │ ├── MethodTestApplication.java │ │ ├── MethodTestSuite.java │ │ ├── NoCacheFoundTest.java │ │ └── TestService.java ├── hazelcast │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ └── examples │ │ │ │ └── hazelcast │ │ │ │ ├── HazelcastApplication.java │ │ │ │ └── TestController.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── hazelcast.xml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── examples │ │ │ └── hazelcast │ │ │ └── HazelcastGeneralSuiteTest.java │ │ └── resources │ │ └── application.yml ├── redis-jedis │ ├── .gitignore │ ├── README.adoc │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ ├── DebugMetricHandler.java │ │ │ │ ├── JedisConfiguration.java │ │ │ │ ├── RedisJedisApplication.java │ │ │ │ ├── TestController.java │ │ │ │ ├── service │ │ │ │ ├── TestService.java │ │ │ │ └── TestServiceImpl.java │ │ │ │ └── servlet │ │ │ │ ├── IpHandlerInterceptor.java │ │ │ │ └── RequestUtils.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── general │ │ │ └── tests │ │ │ └── filter │ │ │ └── servlet │ │ │ ├── JedisGreadyRefillSpeedTest.java │ │ │ ├── JedisIntervalRefillSpeedTest.java │ │ │ └── JedisServletRateLimitTest.java │ │ └── resources │ │ └── application.yml ├── redis-lettuce │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ ├── LettuceConfiguraiton.java │ │ │ │ ├── RedisLettuceApplication.java │ │ │ │ └── TestController.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── general │ │ │ └── tests │ │ │ └── filter │ │ │ └── reactive │ │ │ ├── LettuceGreadyRefillSpeedTest.java │ │ │ ├── LettuceIntervalRefillSpeedTest.java │ │ │ └── LettuceServletRateLimitTest.java │ │ └── resources │ │ └── application.yml ├── redis-redisson │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ ├── DebugMetricHandler.java │ │ │ │ ├── RedisRedissonApplication.java │ │ │ │ ├── RedissonConfiguraiton.java │ │ │ │ └── TestController.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── general │ │ │ └── tests │ │ │ └── filter │ │ │ └── reactive │ │ │ ├── RedissonGreadyRefillSpeedTest.java │ │ │ ├── RedissonIntervalRefillSpeedTest.java │ │ │ └── RedissonServletRateLimitTest.java │ │ └── resources │ │ └── application.yml ├── webflux-infinispan │ ├── .gitignore │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── giffing │ │ │ │ └── bucket4j │ │ │ │ └── spring │ │ │ │ └── boot │ │ │ │ └── starter │ │ │ │ └── examples │ │ │ │ └── webflux │ │ │ │ ├── DebugMetricHandler.java │ │ │ │ ├── MyController.java │ │ │ │ └── WebfluxInfinispanApplication.java │ │ └── resources │ │ │ ├── application.yml │ │ │ └── infinispan.xml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── examples │ │ │ └── webflux │ │ │ └── WebfluxGeneralSuiteTest.java │ │ └── resources │ │ ├── application-webflux-infinispan.yml │ │ └── application.yml └── webflux │ ├── .gitignore │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── giffing │ │ │ └── bucket4j │ │ │ └── spring │ │ │ └── boot │ │ │ └── starter │ │ │ └── examples │ │ │ └── webflux │ │ │ ├── DebugMetricHandler.java │ │ │ ├── MyController.java │ │ │ └── WebfluxApplication.java │ └── resources │ │ ├── application.yml │ │ └── hazelcast.yaml │ └── test │ ├── java │ └── com │ │ └── giffing │ │ └── bucket4j │ │ └── spring │ │ └── boot │ │ └── starter │ │ └── examples │ │ └── webflux │ │ └── WebfluxGeneralSuiteTest.java │ └── resources │ ├── application-webflux.yml │ └── application.yml ├── pom.xml └── src └── main └── doc └── plantuml ├── post_execution_condition.plantuml └── post_execution_condition.png /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Set up JDK 17 11 | uses: actions/setup-java@v1 12 | with: 13 | java-version: 17 14 | - name: Build with Maven 15 | run: mvn -B package --file pom.xml 16 | - name: Publish Test Report 17 | uses: mikepenz/action-junit-report@v4 18 | if: success() || failure() # always run even if the previous step fails 19 | with: 20 | report_paths: '**/target/surefire-reports/TEST-*.xml' 21 | -------------------------------------------------------------------------------- /.github/workflows/pmd.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: pmd 7 | 8 | on: 9 | push: 10 | branches: [ "master" ] 11 | pull_request: 12 | branches: [ "master" ] 13 | schedule: 14 | - cron: '44 10 * * 6' 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | pmd-code-scan: 21 | permissions: 22 | contents: read # for actions/checkout to fetch code 23 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 24 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Set up JDK 17 29 | uses: actions/setup-java@v3 30 | with: 31 | java-version: '17' 32 | distribution: 'temurin' 33 | - name: Run PMD 34 | id: pmd 35 | uses: pmd/pmd-github-action@v2 36 | with: 37 | rulesets: 'rulesets/java/quickstart.xml' 38 | sourcePath: 'src/main/java' 39 | analyzeModifiedFilesOnly: false 40 | - name: Upload SARIF file 41 | uses: github/codeql-action/upload-sarif@v3 42 | with: 43 | sarif_file: pmd-report.sarif 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans 10 | .flattened-pom.xml -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/Bucket4jConfigurationHolder.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; 7 | 8 | import lombok.Data; 9 | 10 | @Data 11 | public class Bucket4jConfigurationHolder { 12 | 13 | private List filterConfiguration = new ArrayList<>(); 14 | 15 | public void addFilterConfiguration(Bucket4JConfiguration filterConfiguration) { 16 | getFilterConfiguration().add(filterConfiguration); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/Condition.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | 4 | /** 5 | * This condition is used to skip or execute a rate limit check. 6 | */ 7 | @FunctionalInterface 8 | public interface Condition { 9 | 10 | /** 11 | * 12 | * @param expressionParams parameters to evaluate the expression 13 | * @return true if the rate limit check should be skipped 14 | */ 15 | boolean evaluate(ExpressionParams expressionParams); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import java.util.Map; 4 | import java.util.function.Predicate; 5 | 6 | /** 7 | * The ExecutePredicate is used to conditionally execute a rate limit. 8 | * 9 | * @param the type of the predicate 10 | */ 11 | public abstract class ExecutePredicate implements Predicate { 12 | 13 | /** 14 | * The unique name of the ExecutionPredicate which can be used 15 | * in the property configuration. 16 | */ 17 | public abstract String name(); 18 | 19 | /** 20 | * Initialize the ExecutionPredicate with the provided 21 | * property configuration. 22 | */ 23 | public ExecutePredicate init(Map args) { 24 | if(hasSimpleConfig(args)) { 25 | parseSimpleConfig(args.get(ExecutePredicateDefinition.SIMPLE_CONFIG_KEY)); 26 | } else { 27 | parseConfig(args); 28 | } 29 | return this; 30 | } 31 | 32 | protected abstract ExecutePredicate parseSimpleConfig(String simpleConfig); 33 | 34 | protected ExecutePredicate parseConfig(Map args) { 35 | throw new UnsupportedOperationException("The ServletRequestExecutionPredicate %s doesn't support arguments" 36 | .formatted(this.getClass().getSimpleName())); 37 | } 38 | 39 | private boolean hasSimpleConfig(Map args) { 40 | return args.containsKey(ExecutePredicateDefinition.SIMPLE_CONFIG_KEY); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExecutePredicateDefinition.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import java.io.Serializable; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | 7 | import jakarta.validation.ValidationException; 8 | import jakarta.validation.constraints.NotNull; 9 | 10 | import org.springframework.validation.annotation.Validated; 11 | 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | @Validated 17 | @Getter 18 | @Setter 19 | @Slf4j 20 | public class ExecutePredicateDefinition implements Serializable { 21 | 22 | public static final String SIMPLE_CONFIG_KEY = "_simple_config_"; 23 | 24 | @NotNull 25 | private String name; 26 | 27 | private final Map args = new LinkedHashMap<>(); 28 | 29 | /** 30 | * Private no arg constructor to enable deserializing serialized predicates with Jacksons ObjectMapper, 31 | * but still let Spring at initialization create the beans through the parameterized constructor. 32 | */ 33 | private ExecutePredicateDefinition(){} 34 | 35 | public ExecutePredicateDefinition(String name) { 36 | int eqIdx = name.indexOf('='); 37 | if (eqIdx <= 0) { 38 | throw new ValidationException( 39 | "Unable to parse ExecutePredicateDefinition text '" + name + "'" + ", must be of the form name=value"); 40 | } 41 | this.name = name.substring(0, eqIdx); 42 | var result = name.substring(eqIdx + 1); 43 | this.args.put(SIMPLE_CONFIG_KEY, result); 44 | log.debug("execute-predicate-simple-config;name:{};value:{}", this.name, result); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExpressionParams.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.expression.Expression; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Parameter information for the evaluation of a Spring {@link Expression} 12 | * 13 | * @param the type of the root object which us used for the SpEl expression. 14 | */ 15 | @Getter 16 | @RequiredArgsConstructor 17 | public class ExpressionParams { 18 | 19 | private final R rootObject; 20 | 21 | private final Map params = new HashMap<>(); 22 | 23 | public ExpressionParams addParam(String name, Object value) { 24 | params.put(name, value); 25 | return this; 26 | } 27 | 28 | public ExpressionParams addParams(Map params) { 29 | this.params.putAll(params); 30 | return this; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/FilterMethod.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | /** 4 | * The filter method defines which type of should be used. 5 | * 6 | */ 7 | public enum FilterMethod { 8 | 9 | /** 10 | * Servlet Request Filter 11 | */ 12 | SERVLET, 13 | 14 | /** 15 | * Spring Boots 5 async WebFilter 16 | */ 17 | WEBFLUX, 18 | 19 | /** 20 | * Spring Cloud Gateway GlobalFilter 21 | */ 22 | 23 | GATEWAY, 24 | 25 | /** 26 | * See GitHub - Extend FilterMehod enum for custom filters (like JMS) #216 27 | */ 28 | JMS 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/IgnoreRateLimiting.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Ignores the rate limiting annotation for a class or method 10 | */ 11 | @Target(value = { ElementType.TYPE, ElementType.METHOD }) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface IgnoreRateLimiting { 14 | } 15 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/KeyFilter.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | /** 4 | * Functional interface to retrieve the Bucket4j key. The key is used to identify the Bucket4j storage. 5 | */ 6 | @FunctionalInterface 7 | public interface KeyFilter { 8 | 9 | /** 10 | * Return the unique Bucket4j storage key. You can think of the key as a unique identifier 11 | * which is for example an IP-Address or a username. The rate limit is then applied to each individual key. 12 | * 13 | * @param expressionParams the expression params 14 | * @return the key to identify the rate limit (IP, username, ...) 15 | */ 16 | String key(ExpressionParams expressionParams); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/PostRateLimitCheck.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | 4 | 5 | /** 6 | * Used to check if the rate limit should be performed independently from the servlet|webflux|gateway request filter 7 | */ 8 | @FunctionalInterface 9 | public interface PostRateLimitCheck { 10 | 11 | /** 12 | * @param request the request information object 13 | * @param response the response information object 14 | * 15 | * @return null if no rate limit should be performed. (maybe skipped or shouldn't be executed). 16 | */ 17 | RateLimitResultWrapper rateLimit(R request, P response); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitCheck.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | 4 | import com.giffing.bucket4j.spring.boot.starter.context.properties.RateLimit; 5 | 6 | /** 7 | * Used to check if the rate limit should be performed independently of the servlet|webflux|gateway request filter 8 | */ 9 | @FunctionalInterface 10 | public interface RateLimitCheck { 11 | 12 | /** 13 | * @param params parameter information 14 | * @param mainRateLimit overwrites the rate limit configuration from the properties 15 | * @return null if no rate limit should be performed. (maybe skipped or shouldn't be executed). 16 | */ 17 | RateLimitResultWrapper rateLimit(ExpressionParams params, RateLimit mainRateLimit); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitConditionMatchingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | /** 4 | * Bad name :-) 5 | *

6 | * If multiple rate limits configured this strategy decides when to stop the evaluation. 7 | */ 8 | public enum RateLimitConditionMatchingStrategy { 9 | 10 | /** 11 | * All rate limits should be evaluated 12 | */ 13 | ALL, 14 | /** 15 | * Only the first matching rate limit will be evaluated 16 | */ 17 | FIRST, 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | /** 4 | * This exception is thrown when the rate limit is reached in the context of a method level when using the 5 | * {@link RateLimiting} annotation. 6 | */ 7 | public class RateLimitException extends RuntimeException { 8 | } 9 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResult.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | /** 7 | * This data class holds the information of a rate limit check. 8 | */ 9 | @Data 10 | @Builder 11 | public class RateLimitResult { 12 | 13 | /** 14 | * If the request was only for estimation without consuming any tokens. 15 | */ 16 | private final boolean estimation; 17 | 18 | /** 19 | * The tokens that are consumed. 20 | *

21 | * If {@link #estimation} is true no tokens are consumed. 22 | */ 23 | private final boolean consumed; 24 | 25 | /** 26 | * The number of tokens that remains until rate limit is executed. 27 | */ 28 | private final long remainingTokens; 29 | 30 | /** 31 | * The time in nanoseconds until the next tokens will be refilled. 32 | */ 33 | private final long nanosToWaitForRefill; 34 | 35 | /** 36 | * The time in nanoseconds until the tokens are refilled to its maximum. 37 | */ 38 | private final long nanosToWaitForReset; 39 | } 40 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResultWrapper.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | /** 8 | * A wrapper class to distinguish the {@link RateLimiting} result between a synchronous and asynchronous call. 9 | *

10 | * If possible we should get rid of it... 11 | */ 12 | @Data 13 | public class RateLimitResultWrapper { 14 | 15 | private RateLimitResult rateLimitResult; 16 | 17 | private CompletableFuture rateLimitResultCompletableFuture; 18 | 19 | public RateLimitResultWrapper(RateLimitResult rateLimitResult) { 20 | this.rateLimitResult = rateLimitResult; 21 | } 22 | 23 | public RateLimitResultWrapper(CompletableFuture rateLimitResultCompletableFuture) { 24 | this.rateLimitResultCompletableFuture = rateLimitResultCompletableFuture; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimiting.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target(value = {ElementType.TYPE, ElementType.METHOD}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Inherited 8 | public @interface RateLimiting { 9 | 10 | /** 11 | * @return The name of the rate limit configuration as a reference to the property file 12 | */ 13 | String name(); 14 | 15 | /** 16 | * The cache key which is mayby modified the e.g. the method name {@link RateLimiting#ratePerMethod()} 17 | * 18 | * @return the cache key. 19 | */ 20 | String cacheKey() default ""; 21 | 22 | /** 23 | * An optional execute condition which overrides the execute condition from the property file 24 | * 25 | * @return the expression in the Spring Expression Language format. 26 | */ 27 | String executeCondition() default ""; 28 | 29 | /** 30 | * An optional execute condition which overrides the execute condition from the property file 31 | * 32 | * @return the expression in the Spring Expression Language format. 33 | */ 34 | String skipCondition() default ""; 35 | 36 | /** 37 | * The Name of the annotated method will be added to the cache key. 38 | * It's maybe a problem 39 | * 40 | * @return true if the method name should be added to the cache key. 41 | */ 42 | boolean ratePerMethod() default false; 43 | 44 | /** 45 | * An optional fall back method which is executed when the rate limit occurs instead of throwing 46 | * the {@link RateLimitException}. 47 | *

48 | * The arguments and the return type of the fallback method must have the same signature. 49 | * 50 | * @return the name of the public method which resists in the same class. 51 | */ 52 | String fallbackMethodName() default ""; 53 | } 54 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RefillSpeed.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context; 2 | 3 | public enum RefillSpeed { 4 | 5 | /** 6 | * Greedily regenerates tokens. 7 | *

8 | * The tokens are refilled as soon as possible. 9 | */ 10 | GREEDY, 11 | 12 | /** 13 | * Regenerates tokens in an interval manner. 14 | *

15 | * The tokens refilled on the specific defined interval. 16 | */ 17 | INTERVAL, 18 | 19 | } 20 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/constraintvalidations/DurationChronoUnitValidator.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations; 2 | 3 | import java.time.temporal.ChronoUnit; 4 | 5 | import jakarta.validation.ConstraintValidator; 6 | import jakarta.validation.ConstraintValidatorContext; 7 | 8 | /** 9 | * This validator is used to check if a ChronoUnit is accepted by the Duration.of() method. 10 | * 11 | * The Duration.of() method does not allow estimated time units, but DAYS is an exception to this rule. 12 | * ChronoUnit treats Days as estimated values because of daylight savings, while Duration.of() treats it 13 | * as an exact value of 24 hours. For this reason the validator also allows Days as valid value. 14 | * 15 | */ 16 | public class DurationChronoUnitValidator implements ConstraintValidator { 17 | 18 | @Override 19 | public boolean isValid(ChronoUnit value, ConstraintValidatorContext context) { 20 | return value != null && (value == ChronoUnit.DAYS || !value.isDurationEstimated()); 21 | } 22 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/constraintvalidations/RateLimitBandWidthIdsValidator.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.properties.BandWidth; 4 | import com.giffing.bucket4j.spring.boot.starter.context.properties.RateLimit; 5 | import io.github.bucket4j.TokensInheritanceStrategy; 6 | import jakarta.validation.ConstraintValidator; 7 | import jakarta.validation.ConstraintValidatorContext; 8 | 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | public class RateLimitBandWidthIdsValidator implements ConstraintValidator { 13 | 14 | @Override 15 | public boolean isValid(RateLimit rateLimit, ConstraintValidatorContext context) { 16 | Set idSet = new HashSet<>(); 17 | 18 | for (BandWidth bandWidth : rateLimit.getBandwidths()) { 19 | String id = bandWidth.getId(); 20 | 21 | if(id == null && rateLimit.getTokensInheritanceStrategy() == TokensInheritanceStrategy.RESET) { 22 | continue; 23 | } 24 | 25 | if (!idSet.add(id)) { 26 | String errorMessage = (id == null) 27 | ? "Multiple bandwidths without id detected. This is only allowed when TokenInheritanceStrategy 'RESET' is applied." 28 | : String.format("Duplicate bandwidth id: %s", id); 29 | 30 | context.disableDefaultConstraintViolation(); 31 | context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation(); 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/constraintvalidations/ValidBandWidthIds.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | 12 | @Constraint(validatedBy = RateLimitBandWidthIdsValidator.class) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.TYPE}) 15 | public @interface ValidBandWidthIds { 16 | String message() default "Duplicate bandwidth id detected"; 17 | 18 | Class[] groups() default {}; 19 | 20 | Class[] payload() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/constraintvalidations/ValidDurationChronoUnit.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | import jakarta.validation.Constraint; 6 | import jakarta.validation.Payload; 7 | 8 | @Documented 9 | @Constraint(validatedBy = DurationChronoUnitValidator.class) 10 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface ValidDurationChronoUnit { 13 | 14 | String message() default "Unsupported duration ChronoUnit. Only time based units are supported (NANOS to DAYS inclusive)."; 15 | 16 | Class[] groups() default {}; 17 | 18 | Class[] payload() default {}; 19 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/constraintvalidations/ValidPredicateNames.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations; 2 | 3 | import jakarta.validation.Constraint; 4 | import jakarta.validation.Payload; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | 12 | @Constraint(validatedBy = Bucket4JConfigurationPredicateNameValidator.class) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.TYPE}) 15 | public @interface ValidPredicateNames { 16 | String message() default "Invalid predicate name"; 17 | 18 | Class[] groups() default {}; 19 | 20 | Class[] payload() default {}; 21 | } 22 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/metrics/MetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.metrics; 2 | 3 | import java.util.List; 4 | 5 | public interface MetricHandler { 6 | 7 | void handle(MetricType type, String name, long counterIncrement, List tags); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/metrics/MetricTagResult.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.metrics; 2 | 3 | import java.util.List; 4 | 5 | import lombok.Data; 6 | 7 | @Data 8 | public class MetricTagResult { 9 | 10 | private String key; 11 | 12 | private String value; 13 | 14 | private List types; 15 | 16 | public MetricTagResult(String key, String value, List types) { 17 | this.key = key; 18 | this.value = value; 19 | this.setTypes(types); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/metrics/MetricType.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.metrics; 2 | 3 | public enum MetricType { 4 | 5 | /** 6 | * Count token consumption. 7 | */ 8 | CONSUMED_COUNTER, 9 | 10 | /** 11 | * Count whenever consumption request for tokens is rejected. 12 | */ 13 | REJECTED_COUNTER, 14 | 15 | /** 16 | * Count parked threads which wait of tokens refill in result of interaction with Bucket4js BlockingBucket. 17 | */ 18 | PARKED_COUNTER, 19 | 20 | /** 21 | * Count interrupted threads during the wait of tokens refill in result of interaction with Bucket4js BlockingBucket 22 | */ 23 | INTERRUPTED_COUNTER, 24 | 25 | /** 26 | * Count delayed tasks was submit to java.util.concurrent.ScheduledExecutorService 27 | * because of wait for tokens refill in result of interaction with SchedulingBucket 28 | */ 29 | DELAYED_COUNTER 30 | } 31 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/BandWidth.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.properties; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RefillSpeed; 4 | import com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations.ValidDurationChronoUnit; 5 | import jakarta.validation.constraints.Min; 6 | import jakarta.validation.constraints.NotNull; 7 | import jakarta.validation.constraints.Positive; 8 | import lombok.Data; 9 | import org.springframework.util.StringUtils; 10 | 11 | import java.io.Serializable; 12 | import java.time.temporal.ChronoUnit; 13 | 14 | /** 15 | * Configures the rate of data which should be transferred. 16 | * 17 | */ 18 | @Data 19 | public class BandWidth implements Serializable { 20 | 21 | private String id; 22 | 23 | public void setId(String id) { 24 | if(StringUtils.hasText(id)){ 25 | this.id = id.trim(); 26 | } 27 | } 28 | 29 | @Positive 30 | private long capacity; 31 | 32 | @Min(1) 33 | private Long refillCapacity; 34 | 35 | @Positive 36 | private long time; 37 | 38 | @NotNull 39 | @ValidDurationChronoUnit 40 | private ChronoUnit unit; 41 | 42 | @Min(1) 43 | private Long initialCapacity; 44 | 45 | @NotNull 46 | private RefillSpeed refillSpeed = RefillSpeed.GREEDY; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/MethodProperties.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.properties; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 4 | import jakarta.validation.constraints.NotBlank; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Data; 7 | import lombok.ToString; 8 | 9 | @Data 10 | @ToString 11 | public class MethodProperties { 12 | 13 | /** 14 | * The name of the configuration to reference in the {@link RateLimiting} annotation. 15 | */ 16 | @NotBlank 17 | private String name; 18 | 19 | /** 20 | * The name of the cache. 21 | */ 22 | @NotBlank 23 | private String cacheName; 24 | 25 | /** 26 | * The rate limit configuration 27 | */ 28 | @NotNull 29 | private RateLimit rateLimit; 30 | 31 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/MetricTag.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.properties; 2 | 3 | import java.io.Serializable; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | 11 | import lombok.Data; 12 | 13 | @Data 14 | public class MetricTag implements Serializable { 15 | 16 | private String key; 17 | 18 | @NotBlank 19 | private String expression; 20 | 21 | private List types = Arrays.asList(MetricType.values()); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/Metrics.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.properties; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Builder 18 | @Data 19 | public class Metrics implements Serializable { 20 | 21 | private boolean enabled = true; 22 | 23 | private List types = Arrays.asList(MetricType.values()); 24 | 25 | private List tags = new ArrayList<>(); 26 | 27 | public Metrics(List metricTags) { 28 | Optional.ofNullable(metricTags).ifPresent(tags -> tags.forEach(tag -> { 29 | this.tags.add(tag); 30 | tag.getTypes().forEach(type -> { 31 | if (!types.contains(type)) { 32 | types.add(type); 33 | } 34 | }); 35 | })); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/qualifier/Gateway.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.qualifier; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Qualifier(Gateway.VALUE) 13 | public @interface Gateway { 14 | String VALUE = "GATEWAY"; 15 | } 16 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/qualifier/Servlet.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.qualifier; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Qualifier(Servlet.VALUE) 13 | public @interface Servlet { 14 | String VALUE = "SERVLET"; 15 | } 16 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/qualifier/Webflux.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.context.qualifier; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Qualifier(Webflux.VALUE) 13 | public @interface Webflux { 14 | String VALUE = "WEBFLUX"; 15 | } 16 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcGiffing/bucket4j-spring-boot-starter/cd19210ee2d6ee8cd225640c9c144df4e3465d59/bucket4j-spring-boot-starter/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/AsyncCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | /** 4 | * The synchronous cache resolver is a marker interface to mark 5 | * async cache implementations. 6 | */ 7 | public interface AsyncCacheResolver extends CacheResolver { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.hazelcast.HazelcastReactiveBucket4jCacheConfiguration; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.hazelcast.HazelcastSpringBucket4jCacheConfiguration; 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.ignite.IgniteBucket4jCacheConfiguration; 6 | import com.giffing.bucket4j.spring.boot.starter.config.cache.infinispan.InfinispanBucket4jCacheConfiguration; 7 | import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.InfinispanJCacheBucket4jConfiguration; 8 | import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.JCacheBucket4jConfiguration; 9 | import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.jedis.JedisBucket4jConfiguration; 10 | import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.lettuce.LettuceBucket4jConfiguration; 11 | import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.redisson.RedissonBucket4jConfiguration; 12 | import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnBucket4jEnabled; 13 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 14 | import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Import; 17 | 18 | @Configuration 19 | @ConditionalOnBucket4jEnabled 20 | @AutoConfigureAfter(CacheAutoConfiguration.class) 21 | @Import(value = { 22 | JCacheBucket4jConfiguration.class, 23 | InfinispanJCacheBucket4jConfiguration.class, 24 | InfinispanBucket4jCacheConfiguration.class, 25 | HazelcastReactiveBucket4jCacheConfiguration.class, 26 | HazelcastSpringBucket4jCacheConfiguration.class, 27 | JedisBucket4jConfiguration.class, 28 | LettuceBucket4jConfiguration.class, 29 | RedissonBucket4jConfiguration.class, 30 | IgniteBucket4jCacheConfiguration.class 31 | }) 32 | public class Bucket4jCacheConfiguration { 33 | } 34 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/CacheManager.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | public interface CacheManager { 4 | V getValue(K key); 5 | void setValue(K key, V value); 6 | } 7 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/CacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | import io.github.bucket4j.distributed.proxy.ProxyManager; 4 | 5 | /** 6 | * The CacheResolver is used to resolve Bucket4js {@link ProxyManager} by 7 | * a given cache name. Each cache implementation should implement this interface. 8 | *

9 | * But the interface shouldn't be implemented directly. The CacheResolver is divided 10 | * to the blocking {@link SyncCacheResolver} and the asynchronous {@link AsyncCacheResolver}. 11 | * 12 | */ 13 | public interface CacheResolver { 14 | 15 | ProxyManagerWrapper resolve(String cacheName); 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/CacheUpdateEvent.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class CacheUpdateEvent { 11 | private K key; 12 | private V oldValue; 13 | private V newValue; 14 | } 15 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/CacheUpdateListener.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | import org.springframework.context.event.EventListener; 4 | import org.springframework.scheduling.annotation.Async; 5 | 6 | public interface CacheUpdateListener { 7 | 8 | @Async 9 | @EventListener(CacheUpdateEvent.class) 10 | void onCacheUpdateEvent(CacheUpdateEvent event); 11 | } 12 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ProxyManagerWrapper.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimitResultWrapper; 4 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricBucketListener; 5 | import io.github.bucket4j.BucketConfiguration; 6 | import io.github.bucket4j.TokensInheritanceStrategy; 7 | 8 | @FunctionalInterface 9 | public interface ProxyManagerWrapper { 10 | 11 | RateLimitResultWrapper tryConsumeAndReturnRemaining( 12 | String key, 13 | Integer numTokens, 14 | boolean isEstimation, 15 | BucketConfiguration bucketConfiguration, 16 | MetricBucketListener metricBucketListener, 17 | long configVersion, 18 | TokensInheritanceStrategy strategy); 19 | } 20 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/SyncCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache; 2 | 3 | /** 4 | * The synchronous cache resolver is a marker interface to mark 5 | * blocking cache implementations. 6 | */ 7 | public interface SyncCacheResolver extends CacheResolver { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastCacheListener.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.hazelcast; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent; 4 | import com.hazelcast.core.EntryEvent; 5 | import com.hazelcast.map.IMap; 6 | import com.hazelcast.map.listener.EntryUpdatedListener; 7 | import org.springframework.context.ApplicationEventPublisher; 8 | 9 | /** 10 | * This class is intended to be used as bean. 11 | *

12 | * It will listen to changes in the cache, parse them to a {@code CacheUpdateEvent} 13 | * and publish the event to the Spring ApplicationEventPublisher. 14 | * 15 | * @param Type of the cache key 16 | * @param Type of the cache value 17 | */ 18 | public class HazelcastCacheListener implements EntryUpdatedListener { 19 | 20 | private ApplicationEventPublisher eventPublisher; 21 | 22 | public HazelcastCacheListener(IMap map, ApplicationEventPublisher eventPublisher) { 23 | map.addEntryListener(this, true); 24 | this.eventPublisher = eventPublisher; 25 | } 26 | 27 | @Override 28 | public void entryUpdated(EntryEvent entryEvent) { 29 | CacheUpdateEvent updateEvent = new CacheUpdateEvent<>(entryEvent.getKey(), entryEvent.getOldValue(), entryEvent.getValue()); 30 | eventPublisher.publishEvent(updateEvent); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.hazelcast; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; 4 | import com.hazelcast.map.IMap; 5 | 6 | public class HazelcastCacheManager implements CacheManager { 7 | 8 | private final IMap map; 9 | 10 | public HazelcastCacheManager(IMap map) { 11 | this.map = map; 12 | } 13 | 14 | @Override 15 | public V getValue(K key) { 16 | return this.map.get(key); 17 | } 18 | 19 | @Override 20 | public void setValue(K key, V value) { 21 | this.map.put(key, value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastCacheResolver.java: -------------------------------------------------------------------------------- 1 | 2 | package com.giffing.bucket4j.spring.boot.starter.config.cache.hazelcast; 3 | 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; 6 | import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; 7 | import com.hazelcast.core.HazelcastInstance; 8 | import com.hazelcast.map.IMap; 9 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 10 | import io.github.bucket4j.distributed.proxy.ProxyManager; 11 | import io.github.bucket4j.grid.hazelcast.HazelcastProxyManager; 12 | 13 | /** 14 | * Creates the {@link ProxyManager} with Bucket4js {@link HazelcastProxyManager} class. 15 | * It uses the {@link HazelcastInstance} to retrieve the needed cache. 16 | * 17 | */ 18 | public class HazelcastCacheResolver extends AbstractCacheResolverTemplate implements SyncCacheResolver, AsyncCacheResolver { 19 | 20 | private final HazelcastInstance hazelcastInstance; 21 | 22 | private final boolean async; 23 | 24 | public HazelcastCacheResolver(HazelcastInstance hazelcastInstance, boolean async) { 25 | this.hazelcastInstance = hazelcastInstance; 26 | this.async = async; 27 | } 28 | 29 | @Override 30 | public String castStringToCacheKey(String key) { 31 | return key; 32 | } 33 | 34 | @Override 35 | public boolean isAsync() { 36 | return async; 37 | } 38 | 39 | @Override 40 | public AbstractProxyManager getProxyManager(String cacheName) { 41 | IMap map = hazelcastInstance.getMap(cacheName); 42 | return new HazelcastProxyManager<>(map); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteCacheListener.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.ignite; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent; 4 | import org.apache.ignite.IgniteCache; 5 | import org.springframework.context.ApplicationEventPublisher; 6 | 7 | import javax.cache.configuration.FactoryBuilder; 8 | import javax.cache.configuration.MutableCacheEntryListenerConfiguration; 9 | import javax.cache.event.CacheEntryEvent; 10 | import javax.cache.event.CacheEntryListenerException; 11 | import javax.cache.event.CacheEntryUpdatedListener; 12 | import java.io.Serializable; 13 | 14 | /** 15 | * This class is intended to be used as bean. 16 | * 17 | * It will listen to changes in the cache, parse them to a {@code CacheUpdateEvent} 18 | * and publish the event to the Spring ApplicationEventPublisher. 19 | * 20 | * @param Type of the cache key 21 | * @param Type of the cache value 22 | */ 23 | public class IgniteCacheListener implements CacheEntryUpdatedListener, Serializable { 24 | 25 | private ApplicationEventPublisher eventPublisher; 26 | 27 | public IgniteCacheListener(IgniteCache cache, ApplicationEventPublisher eventPublisher){ 28 | cache.registerCacheEntryListener( 29 | new MutableCacheEntryListenerConfiguration<> 30 | (FactoryBuilder.factoryOf(this), null, false, false)); 31 | this.eventPublisher = eventPublisher; 32 | } 33 | 34 | @Override 35 | public void onUpdated(Iterable> iterable) throws CacheEntryListenerException { 36 | iterable.forEach(event -> { 37 | CacheUpdateEvent updateEvent = new CacheUpdateEvent<>(event.getKey(), event.getOldValue(), event.getValue()); 38 | eventPublisher.publishEvent(updateEvent); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.ignite; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; 4 | 5 | import org.apache.ignite.IgniteCache; 6 | 7 | public class IgniteCacheManager implements CacheManager { 8 | 9 | private final IgniteCache cache; 10 | 11 | public IgniteCacheManager(IgniteCache cache){ 12 | this.cache = cache; 13 | } 14 | 15 | @Override 16 | public V getValue(K key) { 17 | return cache.get(key); 18 | } 19 | 20 | @Override 21 | public void setValue(K key, V value) { 22 | cache.put(key, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.ignite; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; 5 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 6 | import io.github.bucket4j.grid.ignite.thick.IgniteProxyManager; 7 | import org.apache.ignite.Ignite; 8 | 9 | public class IgniteCacheResolver extends AbstractCacheResolverTemplate implements AsyncCacheResolver { 10 | 11 | private final Ignite ignite; 12 | 13 | public IgniteCacheResolver(Ignite ignite) { 14 | this.ignite = ignite; 15 | } 16 | 17 | @Override 18 | public String castStringToCacheKey(String key) { 19 | return key; 20 | } 21 | 22 | @Override 23 | public boolean isAsync() { 24 | return true; 25 | } 26 | 27 | @Override 28 | public AbstractProxyManager getProxyManager(String cacheName) { 29 | org.apache.ignite.IgniteCache cache = ignite.cache(cacheName); 30 | return new IgniteProxyManager<>(cache); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanCacheListener.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.infinispan; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent; 4 | import org.infinispan.Cache; 5 | import org.infinispan.notifications.Listener; 6 | import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; 7 | import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent; 8 | import org.springframework.context.ApplicationEventPublisher; 9 | 10 | /** 11 | * This class is intended to be used as bean. 12 | *

13 | * It will listen to changes in the cache, parse them to a {@code CacheUpdateEvent} 14 | * and publish the event to the Spring ApplicationEventPublisher. 15 | * 16 | * @param Type of the cache key 17 | * @param Type of the cache value 18 | */ 19 | @Listener 20 | public class InfinispanCacheListener { 21 | 22 | private ApplicationEventPublisher eventPublisher; 23 | 24 | public InfinispanCacheListener(Cache cache, ApplicationEventPublisher eventPublisher) { 25 | cache.addListener(this); 26 | this.eventPublisher = eventPublisher; 27 | } 28 | 29 | @CacheEntryModified 30 | public void entryModified(CacheEntryModifiedEvent event) { 31 | CacheUpdateEvent updateEvent = new CacheUpdateEvent<>(event.getKey(), event.getOldValue(), event.getNewValue()); 32 | this.eventPublisher.publishEvent(updateEvent); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.infinispan; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; 4 | import org.infinispan.Cache; 5 | 6 | public class InfinispanCacheManager implements CacheManager { 7 | 8 | private final Cache cache; 9 | 10 | public InfinispanCacheManager(Cache cache) { 11 | this.cache = cache; 12 | } 13 | @Override 14 | public V getValue(K key) { 15 | return cache.get(key); 16 | } 17 | 18 | @Override 19 | public void setValue(K key, V value) { 20 | this.cache.put(key, value); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.infinispan; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; 5 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 6 | import io.github.bucket4j.grid.infinispan.InfinispanProxyManager; 7 | import org.infinispan.AdvancedCache; 8 | import org.infinispan.Cache; 9 | import org.infinispan.functional.FunctionalMap; 10 | import org.infinispan.functional.impl.FunctionalMapImpl; 11 | import org.infinispan.functional.impl.ReadWriteMapImpl; 12 | import org.infinispan.manager.CacheContainer; 13 | 14 | public class InfinispanCacheResolver extends AbstractCacheResolverTemplate implements AsyncCacheResolver { 15 | 16 | private final CacheContainer cacheContainer; 17 | 18 | public InfinispanCacheResolver(CacheContainer cacheContainer) { 19 | this.cacheContainer = cacheContainer; 20 | } 21 | 22 | @Override 23 | public String castStringToCacheKey(String key) { 24 | return key; 25 | } 26 | 27 | @Override 28 | public boolean isAsync() { 29 | return true; 30 | } 31 | 32 | @Override 33 | public AbstractProxyManager getProxyManager(String cacheName) { 34 | Cache cache = cacheContainer.getCache(cacheName); 35 | return new InfinispanProxyManager<>(toMap(cache)); 36 | } 37 | 38 | private static FunctionalMap.ReadWriteMap toMap(Cache cache) { 39 | AdvancedCache advancedCache = cache.getAdvancedCache(); 40 | FunctionalMapImpl functionalMap = FunctionalMapImpl.create(advancedCache); 41 | return ReadWriteMapImpl.create(functionalMap); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheCacheListener.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.jcache; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent; 4 | import org.springframework.context.ApplicationEventPublisher; 5 | 6 | import javax.cache.Cache; 7 | import javax.cache.configuration.FactoryBuilder; 8 | import javax.cache.configuration.MutableCacheEntryListenerConfiguration; 9 | import javax.cache.event.CacheEntryEvent; 10 | import javax.cache.event.CacheEntryListenerException; 11 | import javax.cache.event.CacheEntryUpdatedListener; 12 | import java.io.Serializable; 13 | 14 | /** 15 | * This class is intended to be used as bean. 16 | *

17 | * It will listen to changes in the cache, parse them to a {@code CacheUpdateEvent} 18 | * and publish the event to the Spring ApplicationEventPublisher. 19 | * 20 | * @param Type of the cache key 21 | * @param Type of the cache value 22 | */ 23 | public class JCacheCacheListener implements CacheEntryUpdatedListener, Serializable { 24 | 25 | private ApplicationEventPublisher eventPublisher; 26 | 27 | public JCacheCacheListener(Cache cache, ApplicationEventPublisher eventPublisher) { 28 | cache.registerCacheEntryListener( 29 | new MutableCacheEntryListenerConfiguration<> 30 | (FactoryBuilder.factoryOf(this), null, true, false)); 31 | this.eventPublisher = eventPublisher; 32 | } 33 | 34 | @Override 35 | public void onUpdated(Iterable> iterable) throws CacheEntryListenerException { 36 | iterable.forEach(event -> { 37 | CacheUpdateEvent updateEvent = new CacheUpdateEvent<>(event.getKey(), event.getOldValue(), event.getValue()); 38 | eventPublisher.publishEvent(updateEvent); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.jcache; 2 | 3 | import javax.cache.Cache; 4 | 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; 6 | 7 | public class JCacheCacheManager implements CacheManager { 8 | 9 | private final Cache cache; 10 | 11 | protected JCacheCacheManager(Cache cache) { 12 | this.cache = cache; 13 | } 14 | 15 | @Override 16 | public V getValue(K key) { 17 | return this.cache.get(key); 18 | } 19 | 20 | @Override 21 | public void setValue(K key, V value) { 22 | this.cache.put(key, value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.jcache; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; 6 | import com.giffing.bucket4j.spring.boot.starter.exception.JCacheNotFoundException; 7 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 8 | import io.github.bucket4j.distributed.proxy.ProxyManager; 9 | import io.github.bucket4j.grid.jcache.JCacheProxyManager; 10 | 11 | import javax.cache.Cache; 12 | import javax.cache.CacheManager; 13 | 14 | /** 15 | * This class is the JCache (JSR-107) implementation of the {@link CacheResolver}. 16 | * It uses Bucket4Js {@link JCacheProxyManager} to implement the {@link ProxyManager}. 17 | * 18 | */ 19 | public class JCacheCacheResolver extends AbstractCacheResolverTemplate implements SyncCacheResolver { 20 | 21 | private final CacheManager cacheManager; 22 | 23 | public JCacheCacheResolver(CacheManager cacheManager) { 24 | this.cacheManager = cacheManager; 25 | } 26 | 27 | @Override 28 | public String castStringToCacheKey(String key) { 29 | return key; 30 | } 31 | 32 | @Override 33 | public boolean isAsync() { 34 | return false; 35 | } 36 | 37 | @Override 38 | public AbstractProxyManager getProxyManager(String cacheName) { 39 | Cache springCache = cacheManager.getCache(cacheName); 40 | if (springCache == null) { 41 | throw new JCacheNotFoundException(cacheName); 42 | } 43 | return new JCacheProxyManager<>(springCache); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/jedis/JedisCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.jedis; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; 6 | import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy; 7 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 8 | import io.github.bucket4j.redis.jedis.cas.JedisBasedProxyManager; 9 | import redis.clients.jedis.JedisPool; 10 | 11 | import java.time.Duration; 12 | 13 | import static java.nio.charset.StandardCharsets.UTF_8; 14 | 15 | /** 16 | * This class is the Redis implementation of the {@link CacheResolver}. 17 | * 18 | */ 19 | public class JedisCacheResolver extends AbstractCacheResolverTemplate implements SyncCacheResolver { 20 | 21 | private final JedisPool pool; 22 | 23 | public JedisCacheResolver(JedisPool pool) { 24 | this.pool = pool; 25 | } 26 | 27 | @Override 28 | public boolean isAsync() { 29 | return false; 30 | } 31 | 32 | @Override 33 | public byte[] castStringToCacheKey(String key) { 34 | return key.getBytes(UTF_8); 35 | } 36 | 37 | @Override 38 | public AbstractProxyManager getProxyManager(String cacheName) { 39 | return JedisBasedProxyManager.builderFor(pool) 40 | .withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10))) 41 | .build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/lettuce/LettuceCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.lettuce; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; 6 | import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy; 7 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 8 | import io.github.bucket4j.redis.lettuce.cas.LettuceBasedProxyManager; 9 | import io.lettuce.core.RedisClient; 10 | 11 | import java.time.Duration; 12 | 13 | import static java.nio.charset.StandardCharsets.UTF_8; 14 | 15 | /** 16 | * This class is the Redis implementation of the {@link CacheResolver}. 17 | */ 18 | public class LettuceCacheResolver extends AbstractCacheResolverTemplate implements AsyncCacheResolver { 19 | 20 | private final RedisClient redisClient; 21 | 22 | public LettuceCacheResolver(RedisClient redisClient) { 23 | this.redisClient = redisClient; 24 | } 25 | 26 | @Override 27 | public boolean isAsync() { 28 | return true; 29 | } 30 | 31 | @Override 32 | public AbstractProxyManager getProxyManager(String cacheName) { 33 | return LettuceBasedProxyManager.builderFor(redisClient) 34 | .withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10))) 35 | .build(); 36 | } 37 | 38 | @Override 39 | public byte[] castStringToCacheKey(String key) { 40 | return key.getBytes(UTF_8); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheListener.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.redisson; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent; 4 | import org.redisson.api.RTopic; 5 | import org.redisson.api.RedissonClient; 6 | import org.springframework.context.ApplicationEventPublisher; 7 | 8 | /** 9 | * This class is intended to be used as bean. 10 | *

11 | * It will listen to Redisson events on the {cacheName}:update channel 12 | * and publish these to the Spring ApplicationEventPublisher as {@code CacheUpdateEvent} 13 | * 14 | * @param Type of the cache key 15 | * @param Type of the cache value 16 | */ 17 | public class RedissonCacheListener { 18 | 19 | private ApplicationEventPublisher eventPublisher; 20 | 21 | public RedissonCacheListener(RedissonClient redisson, String cacheName, ApplicationEventPublisher eventPublisher) { 22 | RTopic pubSubTopic = redisson.getTopic(cacheName); 23 | pubSubTopic.addListener(CacheUpdateEvent.class, this::onCacheUpdateEvent); 24 | this.eventPublisher = eventPublisher; 25 | } 26 | 27 | public void onCacheUpdateEvent(CharSequence channel, CacheUpdateEvent event) { 28 | eventPublisher.publishEvent(event); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.redisson; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent; 5 | import org.redisson.api.RMap; 6 | import org.redisson.api.RTopic; 7 | import org.redisson.api.RedissonClient; 8 | 9 | import java.io.Serializable; 10 | 11 | 12 | public class RedissonCacheManager implements CacheManager { 13 | private final String cacheName; 14 | private final RedissonClient redisson; 15 | private final RTopic pubSubTopic; 16 | 17 | protected RedissonCacheManager(RedissonClient redisson, String cacheName) { 18 | this.cacheName = cacheName; 19 | this.redisson = redisson; 20 | 21 | this.pubSubTopic = redisson.getTopic(cacheName); 22 | } 23 | 24 | @Override 25 | public V getValue(K key) { 26 | RMap map = this.redisson.getMap(this.cacheName); 27 | return map.get(key); 28 | } 29 | 30 | @Override 31 | public void setValue(K key, V value) { 32 | RMap map = this.redisson.getMap(this.cacheName); 33 | V oldValue = map.put(key, value); 34 | 35 | //publish an update event if the key already existed 36 | if (oldValue != null) { 37 | CacheUpdateEvent updateEvent = new CacheUpdateEvent<>(key, oldValue, value); 38 | this.pubSubTopic.publish(updateEvent); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonCacheResolver.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.cache.redis.redisson; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AbstractCacheResolverTemplate; 4 | import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; 5 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; 6 | import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy; 7 | import io.github.bucket4j.distributed.proxy.AbstractProxyManager; 8 | import io.github.bucket4j.redis.redisson.cas.RedissonBasedProxyManager; 9 | import org.redisson.command.CommandAsyncExecutor; 10 | 11 | import java.time.Duration; 12 | 13 | /** 14 | * This class is the Redis implementation of the {@link CacheResolver}. 15 | */ 16 | public class RedissonCacheResolver extends AbstractCacheResolverTemplate implements AsyncCacheResolver { 17 | 18 | private final CommandAsyncExecutor commandExecutor; 19 | 20 | public RedissonCacheResolver(CommandAsyncExecutor commandExecutor) { 21 | this.commandExecutor = commandExecutor; 22 | } 23 | 24 | @Override 25 | public String castStringToCacheKey(String key) { 26 | return key; 27 | } 28 | 29 | @Override 30 | public boolean isAsync() { 31 | return true; 32 | } 33 | 34 | @Override 35 | public AbstractProxyManager getProxyManager(String cacheName) { 36 | return RedissonBasedProxyManager.builderFor(commandExecutor) 37 | .withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10))) 38 | .build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnAsynchronousPropertyCondition.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import org.springframework.context.annotation.Conditional; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * {@link Conditional @Conditional} that only matches when in the Bucket4j properties 9 | * asynchronous configuration exists. E.g. there are reactive filters registered (WEBFLUX, GATEWAY). 10 | */ 11 | @Target({ElementType.TYPE, ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | @Conditional(OnAsynchronousPropertyCondition.class) 15 | public @interface ConditionalOnAsynchronousPropertyCondition { 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnBucket4jEnabled.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Conditional; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * {@link Conditional @Conditional} that matches under the following conditions. 14 | *

    15 | *
  • The 'bucket4j.enabled' is not set.
  • 16 | *
  • The 'bucket4j.enabled' is set to true.
  • 17 | *
18 | */ 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @Target({ElementType.TYPE, ElementType.METHOD}) 21 | @ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, value = {"enabled"}, matchIfMissing = true) 22 | public @interface ConditionalOnBucket4jEnabled { 23 | } 24 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnCache.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Conditional; 6 | import org.springframework.core.annotation.AliasFor; 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * {@link Conditional @Conditional} that matches under the following conditions. 15 | *
    16 | *
  • The 'bucket4j.cache-to-use' is not set.
  • 17 | *
  • The 'bucket4j.cache-to-use' property matches the given {@link #value()}.
  • 18 | *
19 | */ 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target({ ElementType.TYPE, ElementType.METHOD }) 22 | @ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "cache-to-use", matchIfMissing = true) 23 | public @interface ConditionalOnCache { 24 | 25 | @AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue") 26 | String value() default ""; 27 | } 28 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnFilterConfigCacheEnabled.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Conditional; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * {@link Conditional @Conditional} that matches under the following conditions. 14 | *
    15 | *
  • The 'bucket4j.filter-config-caching-enabled' is set to true}.
  • 16 | *
17 | */ 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Target({ ElementType.TYPE, ElementType.METHOD }) 20 | @ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "filter-config-caching-enabled", havingValue = "true") 21 | public @interface ConditionalOnFilterConfigCacheEnabled { 22 | } 23 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnSynchronousPropertyCondition.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 4 | import org.springframework.context.annotation.Conditional; 5 | 6 | import java.lang.annotation.*; 7 | 8 | /** 9 | * {@link Conditional @Conditional} that only matches when in the Bucket4j properties 10 | * a synchronous configuration exists. E.g. the is a method configuration for the @{@link RateLimiting} annotation 11 | * or a Servlet Filter configuration exists. 12 | */ 13 | @Target({ElementType.TYPE, ElementType.METHOD}) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Documented 16 | @Conditional(OnSynchronousPropertyCondition.class) 17 | public @interface ConditionalOnSynchronousPropertyCondition { 18 | } 19 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnAsynchronousPropertyCondition.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.FilterMethod; 4 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; 5 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 6 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 7 | import org.springframework.boot.context.properties.bind.Binder; 8 | import org.springframework.context.annotation.ConditionContext; 9 | import org.springframework.core.type.AnnotatedTypeMetadata; 10 | 11 | import java.util.List; 12 | 13 | public class OnAsynchronousPropertyCondition extends SpringBootCondition { 14 | @Override 15 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 16 | var bucket4jProperties = Binder.get(context.getEnvironment()).bind("bucket4j", Bucket4JBootProperties.class).orElse(null); 17 | if (bucket4jProperties == null) { 18 | return ConditionOutcome.noMatch("@ConditionalOnAsynchronousPropertyCondition Bucket4jBootProperties not configured"); 19 | } 20 | var reactiveFilterConfigurationExists = bucket4jProperties.getFilters() 21 | .stream() 22 | .anyMatch(x -> List.of(FilterMethod.WEBFLUX, FilterMethod.GATEWAY).contains(x.getFilterMethod())); 23 | if (reactiveFilterConfigurationExists) { 24 | return ConditionOutcome.match("@ConditionalOnAsynchronousPropertyCondition Found reactive filter"); 25 | } else { 26 | return ConditionOutcome.noMatch("@ConditionalOnAsynchronousPropertyCondition No filter configuration with FilterMethod.WEBFLUX org FilterMethod.GATEWAY configured"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnSynchronousPropertyCondition.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.condition; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.FilterMethod; 4 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; 5 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 6 | import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 7 | import org.springframework.boot.context.properties.bind.Binder; 8 | import org.springframework.context.annotation.ConditionContext; 9 | import org.springframework.core.type.AnnotatedTypeMetadata; 10 | 11 | public class OnSynchronousPropertyCondition extends SpringBootCondition { 12 | @Override 13 | public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { 14 | var bucket4jProperties = Binder.get(context.getEnvironment()).bind("bucket4j", Bucket4JBootProperties.class).orElse(null); 15 | if (bucket4jProperties == null) { 16 | return ConditionOutcome.noMatch("@ConditionalOnSynchronPropertyCondition Bucket4jBootProperties not configured"); 17 | } 18 | var methodConfigurationExists = !bucket4jProperties.getMethods().isEmpty(); 19 | var servletConfigurationExist = bucket4jProperties.getFilters().stream().anyMatch(x -> x.getFilterMethod().equals(FilterMethod.SERVLET)); 20 | if (methodConfigurationExists || servletConfigurationExist) { 21 | return ConditionOutcome.match("@ConditionalOnSynchronPropertyCondition Found filter method and/or servlet configuration"); 22 | } else { 23 | return ConditionOutcome.noMatch("@ConditionalOnSynchronPropertyCondition No method configuration or filter configuration with FilterMethod.SERVLET configured"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/predicate/HeaderExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.predicate; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.context.ExecutePredicate; 7 | 8 | import lombok.Getter; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | @Getter 13 | public abstract class HeaderExecutePredicate extends ExecutePredicate { 14 | 15 | private String headername; 16 | 17 | private String headerValueRegex; 18 | 19 | public boolean testHeaderValues(List headerValues) { 20 | if(headerValues.isEmpty()) { 21 | return false; 22 | } 23 | var matches = true; 24 | if(headerValueRegex != null) { 25 | matches = headerValues 26 | .stream() 27 | .anyMatch(v -> v.matches(headerValueRegex)); 28 | } 29 | 30 | log.debug("header-predicate;header:{};value:{},result:{}", headername, headerValues, matches); 31 | return matches; 32 | } 33 | 34 | @Override 35 | public String name() { 36 | return "HEADER"; 37 | } 38 | 39 | @Override 40 | public ExecutePredicate parseSimpleConfig(String simpleConfig) { 41 | var headerConfig = Arrays.stream(simpleConfig.split(",")) 42 | .map(String::trim) 43 | .toList(); 44 | if(headerConfig.size() > 2 || headerConfig.isEmpty()) { 45 | throw new IllegalArgumentException("Header Configuration failed"); 46 | } 47 | this.headername = headerConfig.get(0); 48 | if(headerConfig.size() > 1) { 49 | this.headerValueRegex = headerConfig.get(1); 50 | } 51 | return this; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/predicate/MethodExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.predicate; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import com.giffing.bucket4j.spring.boot.starter.context.ExecutePredicate; 8 | 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | @Slf4j 12 | public abstract class MethodExecutePredicate extends ExecutePredicate { 13 | 14 | private List methods = new ArrayList<>(); 15 | 16 | public boolean testRequestMethod(String requestMethod) { 17 | var matches = methods 18 | .stream() 19 | .filter(m -> m.equalsIgnoreCase(requestMethod)) 20 | .findFirst(); 21 | log.debug("method-predicate;methods:{};value:{},result:{}", methods, requestMethod, matches.isPresent()); 22 | return matches.isPresent(); 23 | } 24 | 25 | @Override 26 | public String name() { 27 | return "METHOD"; 28 | } 29 | 30 | @Override 31 | public ExecutePredicate parseSimpleConfig(String simpleConfig) { 32 | this.methods = Arrays.stream(simpleConfig.split(",")) 33 | .map(String::trim) 34 | .toList(); 35 | return this; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/predicate/PathExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.predicate; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import org.springframework.http.server.PathContainer; 8 | import org.springframework.web.util.pattern.PathPattern; 9 | import org.springframework.web.util.pattern.PathPatternParser; 10 | 11 | import com.giffing.bucket4j.spring.boot.starter.context.ExecutePredicate; 12 | 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | @Slf4j 16 | public abstract class PathExecutePredicate extends ExecutePredicate { 17 | 18 | private PathPatternParser pathPatternParser = new PathPatternParser(); 19 | 20 | private List pathPatterns = new ArrayList<>(); 21 | 22 | public boolean testPath(String servletPath) { 23 | PathContainer path = PathContainer.parsePath(servletPath); 24 | var matches = pathPatterns 25 | .stream() 26 | .filter(p -> p.matches(path)) 27 | .findFirst(); 28 | log.debug("path-predicate;path:{};value:{};result:{}", servletPath, pathPatterns, matches.isPresent()); 29 | return matches.isPresent(); 30 | } 31 | 32 | @Override 33 | public String name() { 34 | return "PATH"; 35 | } 36 | 37 | @Override 38 | public ExecutePredicate parseSimpleConfig(String simpleConfig) { 39 | pathPatterns = Arrays.stream(simpleConfig.split(",")) 40 | .map(String::trim) 41 | .map(pathPatternParser::parse) 42 | .toList(); 43 | return this; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/predicate/QueryExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.predicate; 2 | 3 | import java.util.Set; 4 | 5 | import com.giffing.bucket4j.spring.boot.starter.context.ExecutePredicate; 6 | 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | public abstract class QueryExecutePredicate extends ExecutePredicate { 11 | 12 | private String query; 13 | 14 | @Override 15 | public String name() { 16 | return "QUERY"; 17 | } 18 | 19 | public boolean testQueryParameter(Set queryParameters) { 20 | boolean result = queryParameters.contains(query); 21 | log.debug("query-parametetr;value:%s;result:%s".formatted(query, result)); 22 | return result; 23 | } 24 | 25 | @Override 26 | public ExecutePredicate parseSimpleConfig(String simpleConfig) { 27 | this.query = simpleConfig; 28 | return this; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/gateway/Bucket4JAutoConfigurationSpringCloudGatewayFilterBeans.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.gateway; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.Bucket4jConfigurationHolder; 4 | import com.giffing.bucket4j.spring.boot.starter.context.qualifier.Gateway; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class Bucket4JAutoConfigurationSpringCloudGatewayFilterBeans { 10 | 11 | @Bean 12 | @Gateway 13 | public Bucket4jConfigurationHolder gatewayConfigurationHolder() { 14 | return new Bucket4jConfigurationHolder(); 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/predicate/WebfluxExecutePredicateConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.predicate; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 4 | import org.springframework.context.annotation.ComponentScan; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.server.reactive.ServerHttpRequest; 7 | 8 | @Configuration 9 | @ComponentScan 10 | @ConditionalOnClass(ServerHttpRequest.class) 11 | public class WebfluxExecutePredicateConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/predicate/WebfluxHeaderExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.predicate; 2 | 3 | import org.springframework.http.server.reactive.ServerHttpRequest; 4 | import org.springframework.stereotype.Component; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.HeaderExecutePredicate; 7 | 8 | @Component 9 | public class WebfluxHeaderExecutePredicate extends HeaderExecutePredicate{ 10 | 11 | @Override 12 | public boolean test(ServerHttpRequest t) { 13 | return testHeaderValues(t.getHeaders().get(getHeadername())); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/predicate/WebfluxMethodPredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.predicate; 2 | 3 | import org.springframework.http.server.reactive.ServerHttpRequest; 4 | import org.springframework.stereotype.Component; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.MethodExecutePredicate; 7 | 8 | @Component 9 | public class WebfluxMethodPredicate extends MethodExecutePredicate { 10 | 11 | @Override 12 | public boolean test(ServerHttpRequest t) { 13 | return testRequestMethod(t.getMethod().name()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/predicate/WebfluxPathExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.predicate; 2 | 3 | import org.springframework.http.server.reactive.ServerHttpRequest; 4 | import org.springframework.stereotype.Component; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.PathExecutePredicate; 7 | 8 | @Component 9 | public class WebfluxPathExecutePredicate extends PathExecutePredicate{ 10 | 11 | @Override 12 | public boolean test(ServerHttpRequest t) { 13 | return testPath(t.getPath().value()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/predicate/WebfluxQueryExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.predicate; 2 | 3 | import org.springframework.http.server.reactive.ServerHttpRequest; 4 | import org.springframework.stereotype.Component; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.QueryExecutePredicate; 7 | 8 | @Component 9 | public class WebfluxQueryExecutePredicate extends QueryExecutePredicate { 10 | 11 | @Override 12 | public boolean test(ServerHttpRequest t) { 13 | return testQueryParameter(t.getQueryParams().keySet()); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/reactive/webflux/Bucket4JAutoConfigurationWebfluxFilterBeans.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.webflux; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.Bucket4jConfigurationHolder; 4 | import com.giffing.bucket4j.spring.boot.starter.context.qualifier.Webflux; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class Bucket4JAutoConfigurationWebfluxFilterBeans { 10 | 11 | @Bean 12 | @Webflux 13 | public Bucket4jConfigurationHolder servletConfigurationHolder() { 14 | return new Bucket4jConfigurationHolder(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/Bucket4JAutoConfigurationServletFilterBeans.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.Bucket4jConfigurationHolder; 4 | import com.giffing.bucket4j.spring.boot.starter.context.qualifier.Servlet; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class Bucket4JAutoConfigurationServletFilterBeans { 10 | 11 | @Bean 12 | @Servlet 13 | public Bucket4jConfigurationHolder servletConfigurationHolder() { 14 | return new Bucket4jConfigurationHolder(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletHeaderExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.HeaderExecutePredicate; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | 6 | import java.util.Collections; 7 | 8 | public class ServletHeaderExecutePredicate extends HeaderExecutePredicate{ 9 | 10 | @Override 11 | public boolean test(HttpServletRequest t) { 12 | var headerValues = Collections.list(t.getHeaders(getHeadername())) 13 | .stream() 14 | .toList(); 15 | return testHeaderValues(headerValues); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletMethodPredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.MethodExecutePredicate; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | 6 | public class ServletMethodPredicate extends MethodExecutePredicate { 7 | 8 | @Override 9 | public boolean test(HttpServletRequest t) { 10 | return testRequestMethod(t.getMethod()); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletPathExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.PathExecutePredicate; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | 6 | public class ServletPathExecutePredicate extends PathExecutePredicate{ 7 | 8 | @Override 9 | public boolean test(HttpServletRequest t) { 10 | return testPath(t.getServletPath()); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletQueryExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.filter.predicate.QueryExecutePredicate; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | 6 | public class ServletQueryExecutePredicate extends QueryExecutePredicate { 7 | 8 | @Override 9 | public boolean test(HttpServletRequest t) { 10 | return testQueryParameter(t.getParameterMap().keySet()); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/filter/servlet/predicate/ServletRequestExecutePredicateConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.predicate; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | 5 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.Bucket4JAutoConfigurationServletFilter; 11 | 12 | @Configuration 13 | @ConditionalOnClass(HttpServletRequest.class) 14 | @AutoConfigureBefore(Bucket4JAutoConfigurationServletFilter.class) 15 | public class ServletRequestExecutePredicateConfiguration { 16 | 17 | @Bean 18 | ServletHeaderExecutePredicate servletHeaderExecutePredicate() { 19 | return new ServletHeaderExecutePredicate(); 20 | } 21 | 22 | @Bean 23 | ServletMethodPredicate servletMethodPredicate() { 24 | return new ServletMethodPredicate(); 25 | } 26 | 27 | @Bean 28 | ServletPathExecutePredicate servletPathExecutePredicate() { 29 | return new ServletPathExecutePredicate(); 30 | } 31 | 32 | @Bean 33 | ServletQueryExecutePredicate servletQueryExecutePredicate() { 34 | return new ServletQueryExecutePredicate(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/metrics/actuator/Bucket4jMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.metrics.actuator; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 4 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 5 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 6 | import io.micrometer.core.instrument.Metrics; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class Bucket4jMetricHandler implements MetricHandler { 12 | 13 | public static final String METRIC_COUNTER_PREFIX = "bucket4j_summary_"; 14 | 15 | @Override 16 | public void handle(MetricType type, String name, long counterIncrement, List tags) { 17 | 18 | List extendedTags = new ArrayList<>(); 19 | extendedTags.add("name"); 20 | extendedTags.add(name); 21 | 22 | tags.stream().filter(tag -> tag.getTypes().contains(type)).forEach(metricTagResult -> { 23 | extendedTags.add(metricTagResult.getKey()); 24 | extendedTags.add(metricTagResult.getValue()); 25 | }); 26 | 27 | String[] extendedTagsArray = extendedTags.toArray(new String[0]); 28 | 29 | switch (type) { 30 | case CONSUMED_COUNTER: 31 | Metrics 32 | .counter(METRIC_COUNTER_PREFIX + "consumed", extendedTagsArray) 33 | .increment(counterIncrement); 34 | break; 35 | case REJECTED_COUNTER: 36 | Metrics 37 | .counter(METRIC_COUNTER_PREFIX + "rejected", extendedTagsArray) 38 | .increment(counterIncrement); 39 | break; 40 | case PARKED_COUNTER: 41 | Metrics 42 | .counter(METRIC_COUNTER_PREFIX + "parked", extendedTagsArray) 43 | .increment(counterIncrement); 44 | break; 45 | case INTERRUPTED_COUNTER: 46 | Metrics 47 | .counter(METRIC_COUNTER_PREFIX + "interrupted", extendedTagsArray) 48 | .increment(counterIncrement); 49 | break; 50 | case DELAYED_COUNTER: 51 | Metrics 52 | .counter(METRIC_COUNTER_PREFIX + "delayed", extendedTagsArray) 53 | .increment(counterIncrement); 54 | break; 55 | default: 56 | throw new IllegalStateException("Unsupported metric type: " + type); 57 | } 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/metrics/actuator/Bucket4jMetricsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.metrics.actuator; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnBucket4jEnabled; 4 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 5 | import io.micrometer.core.instrument.Metrics; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Primary; 10 | 11 | @Configuration 12 | @ConditionalOnClass(value = {Metrics.class}) 13 | @ConditionalOnBucket4jEnabled 14 | public class Bucket4jMetricsConfiguration { 15 | 16 | @Bean 17 | @Primary 18 | public MetricHandler springBoot2Bucket4jMetricHandler() { 19 | return new Bucket4jMetricHandler(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/metrics/actuator/SpringBootActuatorConfig.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.metrics.actuator; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | 8 | @Configuration 9 | @ConditionalOnClass(value = { Bucket4jEndpoint.class}) 10 | @Import( value = {Bucket4jEndpoint.class, Bucket4jMetricsConfiguration.class}) 11 | public class SpringBootActuatorConfig { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/service/ServiceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.config.service; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.service.ExpressionService; 4 | import com.giffing.bucket4j.spring.boot.starter.service.RateLimitService; 5 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.expression.ExpressionParser; 9 | import org.springframework.expression.spel.SpelCompilerMode; 10 | import org.springframework.expression.spel.SpelParserConfiguration; 11 | import org.springframework.expression.spel.standard.SpelExpressionParser; 12 | 13 | /** 14 | * General Service configuration which can be imported from other autoconfiguration classes. 15 | */ 16 | @Configuration 17 | public class ServiceConfiguration { 18 | 19 | 20 | @Bean 21 | public ExpressionParser expressionParser() { 22 | SpelParserConfiguration config = new SpelParserConfiguration( 23 | SpelCompilerMode.IMMEDIATE, 24 | this.getClass().getClassLoader()); 25 | return new SpelExpressionParser(config); 26 | } 27 | 28 | @Bean 29 | ExpressionService expressionService(ExpressionParser expressionParser, ConfigurableBeanFactory beanFactory) { 30 | return new ExpressionService(expressionParser, beanFactory); 31 | } 32 | 33 | @Bean 34 | RateLimitService rateLimitService(ExpressionService expressionService) { 35 | return new RateLimitService(expressionService); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/Bucket4jGeneralException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.failureanalyzer.Bucket4JAutoConfigFailureAnalyzer; 4 | 5 | /** 6 | * All exceptions should be extend from the this base exception. 7 | * The {@link Bucket4JAutoConfigFailureAnalyzer} uses this class as a base class to analyze 8 | * the exception on startup. 9 | * 10 | */ 11 | public abstract class Bucket4jGeneralException extends RuntimeException { 12 | 13 | private static final long serialVersionUID = 1L; 14 | 15 | protected Bucket4jGeneralException() { 16 | super(); 17 | } 18 | 19 | protected Bucket4jGeneralException(String message) { 20 | super(message); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/ExecutePredicateInstantiationException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class ExecutePredicateInstantiationException extends Bucket4jGeneralException { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | private final String executePredicateName; 11 | 12 | private final Class instantiationException; 13 | 14 | public ExecutePredicateInstantiationException(String executePredicateName, Class instantiationException) { 15 | super("Can't create a new instance for predicate '%s and class %s".formatted(executePredicateName, instantiationException.getName())); 16 | this.executePredicateName = executePredicateName; 17 | this.instantiationException = instantiationException; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/JCacheNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | /** 4 | * This exception should be thrown if no cache was found 5 | */ 6 | public class JCacheNotFoundException extends Bucket4jGeneralException { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | private final String cacheName; 11 | 12 | /** 13 | * @param cacheName the missing cache key 14 | */ 15 | public JCacheNotFoundException(String cacheName) { 16 | super("The cache name '" + cacheName + "' defined in the property is not configured in the caching provider"); 17 | this.cacheName = cacheName; 18 | 19 | } 20 | 21 | public String getCacheName() { 22 | return cacheName; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/NoCacheConfiguredException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * This exception should be thrown if no cache configuration was found. 7 | */ 8 | @Getter 9 | public class NoCacheConfiguredException extends Bucket4jGeneralException { 10 | 11 | private final String cacheToUse; 12 | 13 | public NoCacheConfiguredException(String cacheToUse) { 14 | this.cacheToUse = cacheToUse; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/RateLimitUnknownParameterException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * This exception is thrown when the rate limit is not configured correctly 10 | */ 11 | @RequiredArgsConstructor 12 | @Getter 13 | public class RateLimitUnknownParameterException extends Bucket4jGeneralException { 14 | 15 | private final String expression; 16 | 17 | private final String className; 18 | 19 | private final String methodName; 20 | 21 | private final Set methodParameter; 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/RateLimitingFallbackMethodNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class RateLimitingFallbackMethodNotFoundException extends Bucket4jGeneralException { 9 | 10 | private final String fallbakcMethodName; 11 | 12 | private final String className; 13 | 14 | private final String methodName; 15 | 16 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/RateLimitingFallbackMethodParameterMismatchException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class RateLimitingFallbackMethodParameterMismatchException extends Bucket4jGeneralException { 9 | 10 | private final String fallbackMethodName; 11 | 12 | private final String className; 13 | 14 | private final String methodName; 15 | 16 | private final String parameters; 17 | 18 | private final String fallbackMethodParameters; 19 | 20 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/RateLimitingFallbackReturnTypesMismatchException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class RateLimitingFallbackReturnTypesMismatchException extends Bucket4jGeneralException { 9 | 10 | private final String fallbackMethodName; 11 | 12 | private final String className; 13 | 14 | private final String methodName; 15 | 16 | private final String returnType; 17 | 18 | private final String fallbackMethodReturnType; 19 | 20 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/RateLimitingMethodNameNotConfiguredException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import java.util.Set; 7 | 8 | @RequiredArgsConstructor 9 | @Getter 10 | public class RateLimitingMethodNameNotConfiguredException extends Bucket4jGeneralException { 11 | 12 | private final String name; 13 | 14 | private final Set availableNames; 15 | 16 | private final String className; 17 | 18 | private final String methodName; 19 | 20 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/exception/RateLimitingMultipleFallbackMethodsFoundException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.exception; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class RateLimitingMultipleFallbackMethodsFoundException extends Bucket4jGeneralException { 9 | 10 | private final String fallbakcMethodName; 11 | 12 | private final String className; 13 | 14 | private final String methodName; 15 | 16 | } -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/ReactiveFilterChain.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.filter.reactive; 2 | 3 | import org.springframework.web.server.ServerWebExchange; 4 | 5 | import reactor.core.publisher.Mono; 6 | 7 | @FunctionalInterface 8 | public interface ReactiveFilterChain { 9 | 10 | Mono apply(ServerWebExchange exchange); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/ReactiveRateLimitException.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.filter.reactive; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.server.ResponseStatusException; 5 | 6 | public class ReactiveRateLimitException extends ResponseStatusException { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | public ReactiveRateLimitException(HttpStatus httpStatusCode, String reason) { 11 | super(httpStatusCode, reason); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/gateway/SpringCloudGatewayRateLimitFilter.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.filter.reactive.gateway; 2 | 3 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 4 | import org.springframework.cloud.gateway.filter.GlobalFilter; 5 | import org.springframework.core.Ordered; 6 | import org.springframework.http.server.reactive.ServerHttpRequest; 7 | import org.springframework.http.server.reactive.ServerHttpResponse; 8 | import org.springframework.web.server.ServerWebExchange; 9 | 10 | import com.giffing.bucket4j.spring.boot.starter.context.properties.FilterConfiguration; 11 | import com.giffing.bucket4j.spring.boot.starter.filter.reactive.AbstractReactiveFilter; 12 | 13 | import reactor.core.publisher.Mono; 14 | 15 | /** 16 | * {@link GlobalFilter} to configure Bucket4j on each request. 17 | */ 18 | public class SpringCloudGatewayRateLimitFilter extends AbstractReactiveFilter implements GlobalFilter, Ordered { 19 | 20 | public SpringCloudGatewayRateLimitFilter(FilterConfiguration filterConfig) { 21 | super(filterConfig); 22 | } 23 | 24 | @Override 25 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 26 | ServerHttpRequest request = exchange.getRequest(); 27 | if (urlMatches(request)) { 28 | return chainWithRateLimitCheck(exchange, chain::filter); 29 | } 30 | return chain.filter(exchange); 31 | } 32 | 33 | @Override 34 | public int getOrder() { 35 | return getFilterConfig().getOrder(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/filter/reactive/webflux/WebfluxWebFilter.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.filter.reactive.webflux; 2 | 3 | import org.springframework.core.Ordered; 4 | import org.springframework.http.server.reactive.ServerHttpRequest; 5 | import org.springframework.http.server.reactive.ServerHttpResponse; 6 | import org.springframework.web.server.ServerWebExchange; 7 | import org.springframework.web.server.WebFilter; 8 | import org.springframework.web.server.WebFilterChain; 9 | 10 | import com.giffing.bucket4j.spring.boot.starter.context.properties.FilterConfiguration; 11 | import com.giffing.bucket4j.spring.boot.starter.filter.reactive.AbstractReactiveFilter; 12 | 13 | import reactor.core.publisher.Mono; 14 | 15 | public class WebfluxWebFilter extends AbstractReactiveFilter implements WebFilter, Ordered { 16 | 17 | 18 | public WebfluxWebFilter(FilterConfiguration filterConfig) { 19 | super(filterConfig); 20 | } 21 | 22 | @Override 23 | public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { 24 | ServerHttpRequest request = exchange.getRequest(); 25 | if (urlMatches(request)) { 26 | return chainWithRateLimitCheck(exchange, chain::filter); 27 | } 28 | return chain.filter(exchange); 29 | } 30 | 31 | @Override 32 | public int getOrder() { 33 | return getFilterConfig().getOrder(); 34 | } 35 | 36 | 37 | } 38 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/service/ExpressionService.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.service; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.ExpressionParams; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 7 | import org.springframework.context.expression.BeanFactoryResolver; 8 | import org.springframework.expression.ExpressionParser; 9 | import org.springframework.expression.spel.support.StandardEvaluationContext; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * The expression service wraps Springs {@link ExpressionParser} to execute SpEl expressions. 15 | */ 16 | @RequiredArgsConstructor 17 | @Slf4j 18 | public class ExpressionService { 19 | 20 | private final ExpressionParser expressionParser; 21 | 22 | private final ConfigurableBeanFactory beanFactory; 23 | 24 | public String parseString(String expression, ExpressionParams params) { 25 | var context = getContext(params.getParams()); 26 | var expr = expressionParser.parseExpression(expression); 27 | String result = expr.getValue(context, params.getRootObject(), String.class); 28 | log.debug("parse-string-expression;result:{};expression:{};root:{};params:{}", result, expression, params.getRootObject(), params.getParams()); 29 | return result; 30 | } 31 | 32 | public Boolean parseBoolean(String expression, ExpressionParams params) { 33 | var context = getContext(params.getParams()); 34 | var expr = expressionParser.parseExpression(expression); 35 | boolean result = Boolean.TRUE.equals(expr.getValue(context, params.getRootObject(), Boolean.class)); 36 | log.debug("parse-boolean-expression;result:{};expression:{};root:{};params:{}", result, expression, params.getRootObject(), params.getParams()); 37 | return result; 38 | } 39 | 40 | private StandardEvaluationContext getContext(Map params) { 41 | var context = new StandardEvaluationContext(); 42 | params.forEach(context::setVariable); 43 | context.setBeanResolver(new BeanFactoryResolver(beanFactory)); 44 | return context; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/utils/Bucket4JUtils.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.utils; 2 | 3 | import java.util.Objects; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | 10 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; 11 | 12 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class Bucket4JUtils { 14 | 15 | public static ResponseEntity validateConfigurationUpdate(Bucket4JConfiguration oldConfig, Bucket4JConfiguration newConfig){ 16 | if (oldConfig == null) { 17 | return ResponseEntity.status(HttpStatus.NOT_FOUND).body("No filter with id '" + newConfig.getId() + "' could be found."); 18 | } 19 | 20 | //validate that the version increased 21 | if (oldConfig.getBucket4JVersionNumber() >= newConfig.getBucket4JVersionNumber()) { 22 | return ResponseEntity.badRequest().body("The new configuration should have a higher version than the current configuration."); 23 | } 24 | 25 | //validate that the fields that are not allowed to change remain the same 26 | ResponseEntity response; 27 | response = validateFieldEquality("filterMethod", oldConfig.getFilterMethod(), newConfig.getFilterMethod()); 28 | if (response != null) return response; 29 | response = validateFieldEquality("filterOrder", oldConfig.getFilterOrder(), newConfig.getFilterOrder()); 30 | if (response != null) return response; 31 | return validateFieldEquality("cacheName", oldConfig.getCacheName(), newConfig.getCacheName()); 32 | } 33 | 34 | private static ResponseEntity validateFieldEquality(String fieldName, Object oldValue, Object newValue) { 35 | if (!Objects.equals(oldValue, newValue)) { 36 | String errorMessage = String.format( 37 | "It is not possible to modify the %s of an existing filter. Expected the field to be '%s' but is '%s'.", 38 | fieldName, oldValue, newValue); 39 | return ResponseEntity.badRequest().body(errorMessage); 40 | } 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/utils/RateLimitAopUtils.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.utils; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Method; 5 | 6 | public class RateLimitAopUtils { 7 | 8 | public static R getAnnotationFromMethodOrClass(Method method, Class rateLimitingAnnotation) { 9 | R rateLimitAnnotation; 10 | if(method.getAnnotation(rateLimitingAnnotation) != null) { 11 | rateLimitAnnotation = method.getAnnotation(rateLimitingAnnotation); 12 | } else { 13 | rateLimitAnnotation = method.getDeclaringClass().getAnnotation(rateLimitingAnnotation); 14 | } 15 | return rateLimitAnnotation; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/resources/META-INF/native-image/jni-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"[Lcom.sun.management.internal.DiagnosticCommandArgumentInfo;" 4 | }, 5 | { 6 | "name":"[Lcom.sun.management.internal.DiagnosticCommandInfo;" 7 | }, 8 | { 9 | "name":"com.sun.management.internal.DiagnosticCommandArgumentInfo", 10 | "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","boolean","boolean","boolean","int"] }] 11 | }, 12 | { 13 | "name":"com.sun.management.internal.DiagnosticCommandInfo", 14 | "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","boolean","java.util.List"] }] 15 | }, 16 | { 17 | "name":"java.util.Arrays", 18 | "methods":[{"name":"asList","parameterTypes":["java.lang.Object[]"] }] 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/resources/META-INF/native-image/proxy-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "interfaces":["jakarta.xml.bind.annotation.XmlAccessorType","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 4 | }, 5 | { 6 | "interfaces":["jakarta.xml.bind.annotation.XmlAnyElement","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 7 | }, 8 | { 9 | "interfaces":["jakarta.xml.bind.annotation.XmlElements","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 10 | }, 11 | { 12 | "interfaces":["jakarta.xml.bind.annotation.XmlEnumValue","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 13 | }, 14 | { 15 | "interfaces":["jakarta.xml.bind.annotation.XmlID","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 16 | }, 17 | { 18 | "interfaces":["jakarta.xml.bind.annotation.XmlIDREF","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 19 | }, 20 | { 21 | "interfaces":["jakarta.xml.bind.annotation.XmlSeeAlso","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 22 | }, 23 | { 24 | "interfaces":["jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter","org.glassfish.jaxb.core.v2.model.annotation.Locatable"] 25 | }, 26 | { 27 | "interfaces":["java.security.PrivilegedAction"] 28 | }, 29 | { 30 | "interfaces":["java.sql.Connection"] 31 | }, 32 | { 33 | "interfaces":["java.util.function.Consumer"] 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.diagnostics.FailureAnalyzer=com.giffing.bucket4j.spring.boot.starter.config.failureanalyzer.Bucket4JAutoConfigFailureAnalyzer 2 | -------------------------------------------------------------------------------- /bucket4j-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | com.giffing.bucket4j.spring.boot.starter.Bucket4jStartupCheckConfiguration 2 | com.giffing.bucket4j.spring.boot.starter.config.cache.Bucket4jCacheConfiguration 3 | com.giffing.bucket4j.spring.boot.starter.config.aspect.Bucket4jAopConfig 4 | com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.gateway.Bucket4JAutoConfigurationSpringCloudGatewayFilter 5 | com.giffing.bucket4j.spring.boot.starter.config.filter.servlet.Bucket4JAutoConfigurationServletFilter 6 | com.giffing.bucket4j.spring.boot.starter.config.filter.reactive.webflux.Bucket4JAutoConfigurationWebfluxFilter -------------------------------------------------------------------------------- /examples/caffeine/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/caffeine/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 7 | 8 | @SpringBootApplication 9 | @EnableCaching 10 | @EnableAspectJAutoProxy 11 | public class CaffeineApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(CaffeineApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/caffeine/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CustomQueryExecutePredicate.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.stereotype.Component; 5 | 6 | import com.giffing.bucket4j.spring.boot.starter.context.ExecutePredicate; 7 | 8 | import jakarta.servlet.http.HttpServletRequest; 9 | 10 | @Component 11 | @Slf4j 12 | public class CustomQueryExecutePredicate extends ExecutePredicate { 13 | 14 | private String query; 15 | 16 | @Override 17 | public boolean test(HttpServletRequest t) { 18 | boolean result = t.getParameterMap().containsKey(query); 19 | log.info("query-parametetr;value:%s;result:%s".formatted(query, result)); 20 | return result; 21 | } 22 | 23 | @Override 24 | public String name() { 25 | return "CUSTOM-QUERY"; 26 | } 27 | 28 | @Override 29 | public ExecutePredicate parseSimpleConfig(String simpleConfig) { 30 | this.query = simpleConfig; 31 | return this; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /examples/caffeine/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/DebugMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 10 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 11 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 12 | 13 | @Component 14 | @Slf4j 15 | public class DebugMetricHandler implements MetricHandler { 16 | 17 | @Override 18 | public void handle(MetricType type, String name, long tokens, List tags) { 19 | log.info(String.format("type: %s; name: %s; tags: %s; tokens: %s", 20 | type, 21 | name, 22 | tags 23 | .stream() 24 | .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) 25 | .collect(Collectors.joining(",")), 26 | tokens)); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/caffeine/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/RateLimitExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimitException; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | 9 | @ControllerAdvice 10 | public class RateLimitExceptionHandler { 11 | 12 | @ExceptionHandler(value = {RateLimitException.class}) 13 | protected ResponseEntity handleRateLimit(RateLimitException e) { 14 | return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/caffeine/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/SimpleSecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.filter.OncePerRequestFilter; 11 | 12 | import java.io.IOException; 13 | import java.util.Objects; 14 | 15 | /** 16 | * A user authorized for the url /secure when the query parameter 'username' has the value 'admin' 17 | */ 18 | @Component 19 | @Order(0) 20 | public class SimpleSecurityFilter extends OncePerRequestFilter { 21 | 22 | @Override 23 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 24 | boolean isSecurePath = request.getRequestURI().equals("/secure"); 25 | boolean isNotAdmin = !Objects.equals("admin", request.getParameter("username")); 26 | if(isSecurePath && isNotAdmin) { 27 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 28 | response.getWriter().write("Hello World"); 29 | } else { 30 | filterChain.doFilter(request, response); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/caffeine/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/TestService.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | @Slf4j 9 | public class TestService { 10 | 11 | 12 | @RateLimiting(name = "default", 13 | executeCondition = "#myParamName != 'admin'", 14 | ratePerMethod = true, 15 | fallbackMethodName = "dummy") 16 | public String execute(String myParamName) { 17 | log.info("Method with Param {} executed", myParamName); 18 | return myParamName; 19 | } 20 | 21 | public String dummy(String myParamName) { 22 | log.info("Fallback-Method with Param {} executed", myParamName); 23 | return myParamName; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /examples/caffeine/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | com.giffing.bucket4j: debug 4 | management: 5 | endpoints: 6 | web: 7 | exposure: 8 | include: "*" 9 | security: 10 | enabled: false 11 | spring: 12 | cache: 13 | jcache: 14 | provider: com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider 15 | cache-names: 16 | - buckets 17 | - filterConfigCache 18 | caffeine: 19 | spec: maximumSize=1000000,expireAfterAccess=3600s 20 | bucket4j: 21 | enabled: true 22 | filter-config-caching-enabled: true 23 | filter-config-cache-name: filterConfigCache 24 | methods: 25 | - name: default 26 | cache-name: buckets 27 | rate-limit: 28 | cache-key: 1 29 | bandwidths: 30 | - capacity: 1 31 | refill-capacity: 1 32 | time: 2 33 | unit: seconds 34 | initial-capacity: 1 35 | refill-speed: interval 36 | filters: 37 | - id: filter1 38 | cache-name: buckets 39 | url: .* 40 | rate-limits: 41 | - cache-key: getRemoteAddr() 42 | post-execute-condition: getStatus() eq 200 43 | bandwidths: 44 | - capacity: 10 45 | refill-capacity: 1 46 | time: 10 47 | unit: seconds 48 | initial-capacity: 5 49 | refill-speed: interval 50 | -------------------------------------------------------------------------------- /examples/caffeine/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/caffeine/CaffeineGeneralSuiteTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.caffeine; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.general.tests.method.method.MethodTestSuite; 4 | import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite; 5 | import org.junit.platform.suite.api.SelectClasses; 6 | import org.junit.platform.suite.api.Suite; 7 | 8 | @Suite 9 | @SelectClasses({ 10 | ServletTestSuite.class, 11 | MethodTestSuite.class 12 | }) 13 | public class CaffeineGeneralSuiteTest { 14 | } 15 | -------------------------------------------------------------------------------- /examples/caffeine/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | cache-names: 4 | - buckets 5 | - filterConfigCache 6 | caffeine: 7 | spec: maximumSize=1000000,expireAfterAccess=3600s -------------------------------------------------------------------------------- /examples/ehcache/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/EhcacheApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.ehcache; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class EhcacheApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(EhcacheApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/controller/DebugMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.ehcache.controller; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 10 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 11 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 12 | 13 | @Component 14 | 15 | @Slf4j 16 | public class DebugMetricHandler implements MetricHandler { 17 | 18 | @Override 19 | public void handle(MetricType type, String name, long tokens, List tags) { 20 | log.info(String.format("type: %s; name: %s; tags: %s", 21 | type, 22 | name, 23 | tags 24 | .stream() 25 | .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) 26 | .collect(Collectors.joining(",")))); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/ehcache/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/controller/TestController.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.ehcache.controller; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; 4 | import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; 5 | import jakarta.annotation.Nullable; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping("/") 13 | public class TestController { 14 | 15 | public ResponseEntity secure() { 16 | return ResponseEntity.ok().build(); 17 | } 18 | 19 | @GetMapping("hello") 20 | public ResponseEntity hello() { 21 | return ResponseEntity.ok("Hello World"); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /examples/ehcache/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | management: 2 | endpoints: 3 | web: 4 | exposure: 5 | include: "*" 6 | prometheus: 7 | enabled: true 8 | spring: 9 | cache: 10 | jcache: 11 | config: classpath:ehcache.xml 12 | bucket4j: 13 | enabled: true 14 | filter-config-caching-enabled: true 15 | filter-config-cache-name: filterConfigCache 16 | filters: 17 | - id: filter1 18 | major-version: 1 19 | cache-name: buckets 20 | filter-method: servlet 21 | filter-order: -200 22 | http-content-type: application/json;charset=UTF-8 23 | url: .* 24 | metrics: 25 | tags: 26 | - key: USERNAME 27 | expression: "@securityService.username() != null ? @securityService.username() : 'anonym'" 28 | - key: URL 29 | expression: getRequestURI() 30 | rate-limits: 31 | - bandwidths: 32 | - capacity: 30 33 | time: 1 34 | unit: minutes 35 | - id: filter2 36 | major-version: 1 37 | cache-name: buckets 38 | filter-method: servlet 39 | filter-order: 1 40 | http-content-type: application/json;charset=UTF-8 41 | url: .* 42 | metrics: 43 | tags: 44 | - key: USERNAME 45 | expression: "@securityService.username() != null ? @securityService.username() : 'anonym'" 46 | - key: URL 47 | expression: getRequestURI() 48 | rate-limits: 49 | - execute-condition: "@securityService.username() == 'admin'" 50 | skip-condition: "getRequestURI().contains('filter')" 51 | cache-key: "@securityService.username()?: getRemoteAddr()" 52 | bandwidths: 53 | - capacity: 30 54 | time: 1 55 | unit: minutes 56 | - execute-condition: "@securityService.username() != 'admin'" 57 | skip-condition: "getRequestURI().contains('filter')" 58 | cache-key: "@securityService.username()?: getRemoteAddr()" 59 | bandwidths: 60 | - capacity: 5 61 | time: 1 62 | unit: minutes -------------------------------------------------------------------------------- /examples/ehcache/src/main/resources/ehcache.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 3600 9 | 10 | 1000000 11 | 12 | 13 | 14 | 1000000 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/ehcache/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/ehcache/EhcacheGeneralSuiteTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.ehcache; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite; 4 | import org.junit.platform.suite.api.SelectClasses; 5 | import org.junit.platform.suite.api.Suite; 6 | 7 | @Suite 8 | @SelectClasses({ 9 | ServletTestSuite.class, 10 | }) 11 | public class EhcacheGeneralSuiteTest { 12 | } 13 | -------------------------------------------------------------------------------- /examples/ehcache/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | jcache: 4 | config: classpath:ehcache.xml -------------------------------------------------------------------------------- /examples/gateway/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/gateway/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/gateway/GatewayMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.gateway; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 10 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 11 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 12 | 13 | 14 | @Component 15 | @Slf4j 16 | public class GatewayMetricHandler implements MetricHandler { 17 | 18 | @Override 19 | public void handle(MetricType type, String name, long tokens, List tags) { 20 | log.info(String.format("name: %s;type: %s; tokens: %s", type, name, tokens)); 21 | log.info("\t" + tags.stream().map(mt -> mt.getKey() + ":" + mt.getValue()).collect(Collectors.joining(","))); 22 | log.info("################"); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /examples/gateway/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/gateway/GatewaySampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.gateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | import org.springframework.cache.annotation.EnableCaching; 7 | import org.springframework.cloud.gateway.route.RouteLocator; 8 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 9 | import org.springframework.context.annotation.Bean; 10 | 11 | 12 | @SpringBootApplication 13 | @EnableConfigurationProperties(UriConfiguration.class) 14 | @EnableCaching 15 | public class GatewaySampleApplication { 16 | 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(GatewaySampleApplication.class, args); 20 | } 21 | 22 | @Bean 23 | public RouteLocator customRouteLocator(RouteLocatorBuilder builder, UriConfiguration uriConfiguration) { 24 | //@formatter:off 25 | String httpUri = uriConfiguration.getHttpbin(); 26 | return builder.routes() 27 | .route(p -> p 28 | .path("/hello") 29 | .filters(f -> f.addRequestHeader("Hello", "World")) 30 | .uri(httpUri)) 31 | .build(); 32 | //@formatter:on 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/gateway/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/gateway/UriConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.gateway; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | 6 | @ConfigurationProperties 7 | public class UriConfiguration { 8 | 9 | private String httpbin = "http://httpbin.org:80"; 10 | 11 | public String getHttpbin() { 12 | return httpbin; 13 | } 14 | 15 | public void setHttpbin(String httpbin) { 16 | this.httpbin = httpbin; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | type: hazelcast 4 | management: 5 | endpoints: 6 | web: 7 | exposure: 8 | include: "*" 9 | bucket4j: 10 | filter-config-caching-enabled: true 11 | filter-config-cache-name: filterConfigCache 12 | default-metric-tags: 13 | - expression: "1" 14 | key: xx 15 | filters: 16 | - id: filter1 17 | major-version: 1 18 | metrics: 19 | types: 20 | - consumed-counter 21 | - rejected-counter 22 | tags: 23 | - key: xx 24 | expression: "2" 25 | cache-name: buckets 26 | filter-method: gateway 27 | url: .* 28 | filter-order: -100000 29 | rate-limits: 30 | - tokens-inheritance-strategy: reset 31 | bandwidths: 32 | - capacity: 5 33 | time: 10 34 | unit: seconds 35 | refill-speed: interval 36 | - capacity: 43200 37 | time: 1 38 | unit: days 39 | refill-speed: interval -------------------------------------------------------------------------------- /examples/gateway/src/main/resources/hazelcast.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 127.0.0.1 11 | 12 | 13 | 14 | 15 | 16 | 120 17 | BINARY 18 | CREATE_ON_UPDATE 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 120 27 | BINARY 28 | CREATE_ON_UPDATE 29 | true 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/general-tests/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/ReactiveTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class ReactiveTestApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ReactiveTestApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/WebfluxTestSuite.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import org.junit.platform.suite.api.SelectClasses; 4 | import org.junit.platform.suite.api.Suite; 5 | 6 | @Suite 7 | @SelectClasses({ 8 | ReactiveRateLimitTest.class, 9 | ReactiveGreadyRefillSpeedTest.class, 10 | ReactiveIntervalRefillSpeedTest.class, 11 | }) 12 | public class WebfluxTestSuite { 13 | } 14 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/AddResponseHeaderTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.Collections; 12 | import java.util.stream.IntStream; 13 | 14 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.webRequestWithStatus; 15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 18 | 19 | 20 | @SpringBootTest(properties = { 21 | "bucket4j.filters[0].cache-name=buckets", 22 | "bucket4j.filters[0].url=.*", 23 | "bucket4j.filters[0].http-response-headers.hello=world", 24 | "bucket4j.filters[0].http-response-headers.abc=cba", 25 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].capacity=5", 26 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].time=10", 27 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds", 28 | }) 29 | @AutoConfigureMockMvc 30 | @DirtiesContext 31 | public class AddResponseHeaderTest { 32 | 33 | @Autowired 34 | private MockMvc mockMvc; 35 | 36 | @Test 37 | void assert_custom_response_header() throws Exception { 38 | String url = "/hello"; 39 | IntStream.rangeClosed(1, 5) 40 | .boxed() 41 | .sorted(Collections.reverseOrder()) 42 | .forEach(counter -> webRequestWithStatus(mockMvc, url, HttpStatus.OK, counter - 1)); 43 | 44 | mockMvc 45 | .perform(get(url)) 46 | .andExpect(status().is(HttpStatus.TOO_MANY_REQUESTS.value())) 47 | .andExpect(header().exists("X-Rate-Limit-Retry-After-Seconds")) 48 | .andExpect(header().string("hello", "world")) 49 | .andExpect(header().string("abc", "cba")); 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/Bucket4jDisabledTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.annotation.DirtiesContext; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | 10 | import java.util.Collections; 11 | import java.util.stream.IntStream; 12 | 13 | import static org.hamcrest.Matchers.containsString; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 16 | 17 | 18 | @SpringBootTest(properties = { 19 | "bucket4j.enabled=false", 20 | "bucket4j.filters[0].cache-name=buckets", 21 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].capacity=5", 22 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].time=10", 23 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds", 24 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].refill-speed=interval", 25 | "bucket4j.filters[0].url=^(/hello).*", 26 | }) 27 | @AutoConfigureMockMvc 28 | @DirtiesContext 29 | public class Bucket4jDisabledTest { 30 | 31 | @Autowired 32 | private MockMvc mockMvc; 33 | 34 | @Test 35 | void helloTest() { 36 | String url = "/hello"; 37 | IntStream.rangeClosed(1, 50) 38 | .boxed() 39 | .sorted(Collections.reverseOrder()) 40 | .forEach(counter -> { 41 | try { 42 | mockMvc 43 | .perform(get(url)) 44 | .andExpect(status().isOk()) 45 | .andExpect(header().doesNotExist("X-Rate-Limit-Remaining")) 46 | .andExpect(content().string(containsString("Hello World"))); 47 | } catch (Exception e) { 48 | throw new RuntimeException(e); 49 | } 50 | }); 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/ChangeResponseHttpStatusCodeTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.Collections; 12 | import java.util.stream.IntStream; 13 | 14 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.blockedWebRequestWithStatus; 15 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.webRequestWithStatus; 16 | 17 | 18 | @SpringBootTest(properties = { 19 | "bucket4j.filters[0].cache-name=buckets", 20 | "bucket4j.filters[0].url=.*", 21 | "bucket4j.filters[0].http-status-code=NOT_FOUND", 22 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].capacity=5", 23 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].time=10", 24 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds", 25 | }) 26 | @AutoConfigureMockMvc 27 | @DirtiesContext 28 | public class ChangeResponseHttpStatusCodeTest { 29 | 30 | @Autowired 31 | private MockMvc mockMvc; 32 | 33 | @Test 34 | void assert_response_http_status_code() throws Exception { 35 | String url = "/hello"; 36 | IntStream.rangeClosed(1, 5) 37 | .boxed() 38 | .sorted(Collections.reverseOrder()) 39 | .forEach(counter -> webRequestWithStatus(mockMvc, url, HttpStatus.OK, counter - 1)); 40 | blockedWebRequestWithStatus(mockMvc, url, HttpStatus.NOT_FOUND); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/EmptyHttpResponseTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.Collections; 12 | import java.util.stream.IntStream; 13 | 14 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.blockedWebRequestDueToRateLimitWithEmptyBody; 15 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.webRequestWithStatus; 16 | 17 | 18 | @SpringBootTest(properties = { 19 | "bucket4j.filters[0].cache-name=buckets", 20 | "bucket4j.filters[0].url=^(/hello).*", 21 | "bucket4j.filters[0].http-response-body=null", 22 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].capacity=5", 23 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].time=10", 24 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds", 25 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].refill-speed=interval", 26 | }) 27 | @AutoConfigureMockMvc 28 | @DirtiesContext 29 | public class EmptyHttpResponseTest { 30 | 31 | @Autowired 32 | private MockMvc mockMvc; 33 | 34 | @Test 35 | void assert_empty_response() throws Exception { 36 | String url = "/hello"; 37 | IntStream.rangeClosed(1, 5) 38 | .boxed() 39 | .sorted(Collections.reverseOrder()) 40 | .forEach(counter -> webRequestWithStatus(mockMvc, url, HttpStatus.OK, counter - 1)); 41 | blockedWebRequestDueToRateLimitWithEmptyBody(mockMvc, url); 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/GreadyRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.Collections; 12 | import java.util.stream.IntStream; 13 | 14 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.blockedWebRequestDueToRateLimit; 15 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.webRequestWithStatus; 16 | 17 | 18 | @SpringBootTest(properties = { 19 | "bucket4j.filters[0].cache-name=buckets", 20 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].capacity=5", 21 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].time=10", 22 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds", 23 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].refill-speed=greedy", 24 | "bucket4j.filters[0].url=^(/hello).*", 25 | }) 26 | @AutoConfigureMockMvc 27 | @DirtiesContext 28 | public class GreadyRefillSpeedTest { 29 | 30 | @Autowired 31 | private MockMvc mockMvc; 32 | 33 | @Test 34 | void helloTest() throws Exception { 35 | String url = "/hello"; 36 | IntStream.rangeClosed(1, 5) 37 | .boxed() 38 | .sorted(Collections.reverseOrder()) 39 | .forEach(counter -> webRequestWithStatus(mockMvc, url, HttpStatus.OK, counter - 1)); 40 | blockedWebRequestDueToRateLimit(mockMvc, url); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/IntervalRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.test.annotation.DirtiesContext; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | 11 | import java.util.Collections; 12 | import java.util.stream.IntStream; 13 | 14 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.blockedWebRequestDueToRateLimit; 15 | import static com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.MockMvcHelper.webRequestWithStatus; 16 | 17 | 18 | @SpringBootTest(properties = { 19 | "bucket4j.filters[0].cache-name=buckets", 20 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].capacity=5", 21 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].time=10", 22 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].unit=seconds", 23 | "bucket4j.filters[0].rate-limits[0].bandwidths[0].refill-speed=interval", 24 | "bucket4j.filters[0].url=^(/hello).*", 25 | }) 26 | @AutoConfigureMockMvc 27 | @DirtiesContext 28 | public class IntervalRefillSpeedTest { 29 | 30 | @Autowired 31 | private MockMvc mockMvc; 32 | 33 | @Test 34 | void helloTest() throws Exception { 35 | String url = "/hello"; 36 | IntStream.rangeClosed(1, 5) 37 | .boxed() 38 | .sorted(Collections.reverseOrder()) 39 | .forEach(counter -> webRequestWithStatus(mockMvc, url, HttpStatus.OK, counter - 1)); 40 | blockedWebRequestDueToRateLimit(mockMvc, url); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/ServletTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class ServletTestApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ServletTestApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/ServletTestSuite.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import org.junit.platform.suite.api.SelectClasses; 4 | import org.junit.platform.suite.api.Suite; 5 | 6 | @Suite 7 | @SelectClasses({ 8 | ServletRateLimitTest.class, 9 | GreadyRefillSpeedTest.class, 10 | IntervalRefillSpeedTest.class, 11 | PostExecuteConditionTest.class, 12 | EmptyHttpResponseTest.class, 13 | ChangeResponseHttpStatusCodeTest.class, 14 | AddResponseHeaderTest.class, 15 | SkipConditionTest.class, 16 | ExecuteConditionTest.class, 17 | Bucket4jDisabledTest.class 18 | }) 19 | public class ServletTestSuite { 20 | } 21 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/security/SimpleSecurityFilter.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.security; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.filter.OncePerRequestFilter; 11 | 12 | import java.io.IOException; 13 | import java.util.Objects; 14 | 15 | /** 16 | * A user authorized for the url /secure when the query parameter 'username' has the value 'admin' 17 | */ 18 | @Component 19 | @Order(0) 20 | public class SimpleSecurityFilter extends OncePerRequestFilter { 21 | 22 | @Override 23 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 24 | boolean isSecurePath = request.getRequestURI().equals("/secure"); 25 | boolean isNotAdmin = !Objects.equals("admin", request.getParameter("username")); 26 | if(isSecurePath && isNotAdmin) { 27 | response.setStatus(HttpStatus.UNAUTHORIZED.value()); 28 | response.getWriter().write("Hello World"); 29 | } else { 30 | filterChain.doFilter(request, response); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/Bucket4jDisabledTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.annotation.DirtiesContext; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertAll; 10 | 11 | @SpringBootTest(properties = { 12 | "bucket4j.enabled=false", 13 | "bucket4j.methods[0].name=default", 14 | "bucket4j.methods[0].cache-name=buckets", 15 | "bucket4j.methods[0].rate-limit.bandwidths[0].capacity=5", 16 | "bucket4j.methods[0].rate-limit.bandwidths[0].time=10", 17 | "bucket4j.methods[0].rate-limit.bandwidths[0].unit=seconds", 18 | "bucket4j.methods[0].rate-limit.bandwidths[0].refill-speed=greedy", 19 | }) 20 | @RequiredArgsConstructor 21 | @DirtiesContext 22 | public class Bucket4jDisabledTest { 23 | 24 | @Autowired 25 | private TestService testService; 26 | 27 | @Test 28 | public void assert_no_rate_limit_if_bucket4j_is_disabled() { 29 | for(int i = 0; i < 50; i++) { 30 | assertAll(() -> testService.withExecuteCondition("normal_user")); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/ClassLevelTestService.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.IgnoreRateLimiting; 4 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Used to test a @{@link RateLimiting} annotation on class level. 10 | */ 11 | @Component 12 | @Slf4j 13 | @RateLimiting(name = "default") 14 | public class ClassLevelTestService { 15 | 16 | public void notAnnotatedMethod() { 17 | log.info("Method notAnnotatedMethod"); 18 | } 19 | 20 | /** 21 | * Method should be ignored from rate limiting. 22 | */ 23 | @IgnoreRateLimiting 24 | public void ignoreMethod() { 25 | log.info("Method ignoreMethod"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/IgnoreOnClassLevelTestService.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.IgnoreRateLimiting; 4 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * No rate limiting should be executed on any method if the class is annotated with @{@link IgnoreRateLimiting} 10 | */ 11 | @Component 12 | @Slf4j 13 | @IgnoreRateLimiting 14 | public class IgnoreOnClassLevelTestService { 15 | 16 | @RateLimiting(name = "default") 17 | public void execute() { 18 | log.info("Method execute"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 7 | 8 | @SpringBootApplication 9 | @EnableCaching 10 | @EnableAspectJAutoProxy 11 | public class MethodTestApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(MethodTestApplication.class, args); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/MethodTestSuite.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.general.tests.method.failures.RateLimitConfigurationStartupFailuresTest; 4 | import org.junit.platform.suite.api.SelectClasses; 5 | import org.junit.platform.suite.api.Suite; 6 | 7 | @Suite 8 | @SelectClasses({ 9 | MethodRateLimitTest.class, 10 | NoCacheFoundTest.class, 11 | RateLimitConfigurationStartupFailuresTest.class, 12 | Bucket4jDisabledTest.class 13 | }) 14 | public class MethodTestSuite { 15 | } 16 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/NoCacheFoundTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.exception.NoCacheConfiguredException; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.test.annotation.DirtiesContext; 8 | 9 | import java.util.Properties; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | @DirtiesContext 15 | public class NoCacheFoundTest { 16 | 17 | @Test 18 | public void assert_startup_failure_when_cache_not_configured() { 19 | SpringApplication springApplication = new SpringApplication(MethodTestApplication.class); 20 | Properties properties = new Properties(); 21 | properties.put("bucket4j.cache-to-use", "does_not_exist"); 22 | properties.put("bucket4j.methods[0].name", "default"); 23 | properties.put("bucket4j.methods[0].cache-name", "buckets"); 24 | properties.put("bucket4j.methods[0].rate-limit.bandwidths[0].capacity", "5"); 25 | properties.put("bucket4j.methods[0].rate-limit.bandwidths[0].time", "10"); 26 | properties.put("bucket4j.methods[0].rate-limit.bandwidths[0].unit", "seconds"); 27 | properties.put("bucket4j.methods[0].rate-limit.bandwidths[0].refill-speed", "greedy"); 28 | springApplication.setDefaultProperties(properties); 29 | 30 | var noCacheConfiguredException = Assertions.assertThrows(NoCacheConfiguredException.class, springApplication::run); 31 | assertNotNull(noCacheConfiguredException); 32 | assertEquals("does_not_exist", noCacheConfiguredException.getCacheToUse()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/TestService.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.method.method; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Slf4j 9 | public class TestService { 10 | 11 | 12 | @RateLimiting( 13 | name = "default", 14 | executeCondition = "#myParamName != 'admin'") 15 | public String withExecuteCondition(String myParamName) { 16 | log.info("Method withExecuteCondition with Param {} executed", myParamName); 17 | return myParamName; 18 | } 19 | 20 | @RateLimiting( 21 | name = "default", 22 | skipCondition = "#myParamName eq 'admin'") 23 | public String withSkipCondition(String myParamName) { 24 | log.info("Method withSkipCondition with Param {} executed", myParamName); 25 | return myParamName; 26 | } 27 | 28 | @RateLimiting( 29 | name = "default", 30 | cacheKey = "#cacheKey") 31 | public String withCacheKey(String cacheKey) { 32 | log.info("Method withCacheKey with Param {} executed", cacheKey); 33 | return cacheKey; 34 | } 35 | 36 | @RateLimiting( 37 | name = "default", 38 | ratePerMethod = true) 39 | public String withRatePerMethod1(String cacheKey) { 40 | log.info("Method withRatePerMethod1 with Param {} executed", cacheKey); 41 | return cacheKey; 42 | } 43 | 44 | @RateLimiting( 45 | name = "default", 46 | ratePerMethod = true) 47 | public String withRatePerMethod2(String cacheKey) { 48 | log.info("Method withRatePerMethod1 with Param {} executed", cacheKey); 49 | return cacheKey; 50 | } 51 | 52 | 53 | @RateLimiting(name = "default", cacheKey = "'normal'", fallbackMethodName = "fallbackMethod") 54 | public String withFallbackMethod(String myParamName) { 55 | return "normal-method-executed;param:" + myParamName; 56 | } 57 | 58 | public String fallbackMethod(String myParamName) { 59 | return "fallback-method-executed;param:" + myParamName; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /examples/hazelcast/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/hazelcast/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/hazelcast/HazelcastApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.hazelcast; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class HazelcastApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(HazelcastApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/hazelcast/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | type: jcache 4 | jcache: 5 | provider: com.hazelcast.cache.impl.HazelcastServerCachingProvider 6 | main: 7 | allow-bean-definition-overriding: true 8 | bucket4j: 9 | enabled: true 10 | filter-config-caching-enabled: true 11 | filter-config-cache-name: filterConfigCache 12 | filters: 13 | - id: filter1 14 | cache-name: buckets 15 | url: .* 16 | major-version: 1 17 | rate-limits: 18 | - tokens-inheritance-strategy: reset 19 | skip-condition: "getRequestURI().contains('filter')" 20 | bandwidths: 21 | - capacity: 5 22 | time: 10 23 | unit: seconds 24 | refill-speed: interval -------------------------------------------------------------------------------- /examples/hazelcast/src/main/resources/hazelcast.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | true 6 | 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 127.0.0.1 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/hazelcast/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/hazelcast/HazelcastGeneralSuiteTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.hazelcast; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet.ServletTestSuite; 4 | import org.junit.platform.suite.api.SelectClasses; 5 | import org.junit.platform.suite.api.Suite; 6 | 7 | @Suite 8 | @SelectClasses({ 9 | ServletTestSuite.class 10 | }) 11 | public class HazelcastGeneralSuiteTest { 12 | } 13 | -------------------------------------------------------------------------------- /examples/hazelcast/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | type: jcache 4 | jcache: 5 | provider: com.hazelcast.cache.impl.HazelcastServerCachingProvider 6 | main: 7 | allow-bean-definition-overriding: true -------------------------------------------------------------------------------- /examples/redis-jedis/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /examples/redis-jedis/README.adoc: -------------------------------------------------------------------------------- 1 | = Bucket4j redis-jedis example 2 | 3 | == Introduction 4 | 5 | This example can be locally executed to examine the jedis-redis implementation. 6 | 7 | This example contains rate limit settings as ServletFilter and as Annotation. 8 | 9 | To run the example locally you need: 10 | 11 | - JDK 17 12 | - docker 13 | 14 | == Start Redis / KeyDB 15 | 16 | Start a local KeyDB (compatible with Redis) in a terminal / shell with available docker. 17 | 18 | [source,bash] 19 | ---- 20 | docker run -d -p 6379:6379 eqalpha/keydb 21 | ---- 22 | 23 | == Start RedisJedisApplication 24 | 25 | Just start RedisJedisApplication in your application. 26 | 27 | == URLs 28 | 29 | |=== 30 | |Method|URL|Testcase 31 | 32 | |GET 33 | |http://localhost:8080/hello 34 | |RateLimit done by ServletFilter for filter1 35 | 36 | |GET 37 | |http://localhost:8080/world 38 | |RateLimit done by ServletFilter for filter2 39 | 40 | |GET 41 | |http://localhost:8080/greeting 42 | |RateLimit done by Annotation having fallback method 43 | 44 | |GET 45 | |http://localhost:8080/actuator/metrics/bucket4j_summary_consumed 46 | |metric for consumed and not blocked requests 47 | 48 | |GET 49 | |http://localhost:8080/actuator/metrics/bucket4j_summary_rejected 50 | |metric for rejected (blocked or fallback) requests 51 | 52 | 53 | |=== 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 4 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 5 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @Component 13 | @Slf4j 14 | public class DebugMetricHandler implements MetricHandler { 15 | 16 | @Override 17 | public void handle(MetricType type, String name, long tokens, List tags) { 18 | log.info(String.format("type: %s; name: %s; tags: %s; tokens: %s", 19 | type, 20 | name, 21 | tags 22 | .stream() 23 | .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) 24 | .collect(Collectors.joining(",")), 25 | tokens)); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/JedisConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.servlet.IpHandlerInterceptor; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | import redis.clients.jedis.JedisPool; 10 | import redis.clients.jedis.JedisPoolConfig; 11 | 12 | import java.time.Duration; 13 | 14 | @Configuration 15 | public class JedisConfiguration implements WebMvcConfigurer { 16 | 17 | @Bean 18 | public JedisPool jedisPool(@Value("${spring.data.redis.port}") String port) { 19 | final JedisPoolConfig poolConfig = buildPoolConfig(); 20 | return new JedisPool(poolConfig, "localhost", Integer.parseInt(port)); 21 | } 22 | 23 | private JedisPoolConfig buildPoolConfig() { 24 | final JedisPoolConfig poolConfig = new JedisPoolConfig(); 25 | poolConfig.setMaxTotal(128); 26 | poolConfig.setMaxIdle(128); 27 | poolConfig.setMinIdle(16); 28 | poolConfig.setTestOnBorrow(true); 29 | poolConfig.setTestOnReturn(true); 30 | poolConfig.setTestWhileIdle(true); 31 | poolConfig.setMinEvictableIdleDuration(Duration.ofSeconds(60)); 32 | poolConfig.setTimeBetweenEvictionRuns(Duration.ofSeconds(30)); 33 | poolConfig.setNumTestsPerEvictionRun(3); 34 | poolConfig.setBlockWhenExhausted(true); 35 | return poolConfig; 36 | } 37 | 38 | /** 39 | * Add Spring MVC lifecycle interceptors for pre- and post-processing of 40 | * controller method invocations and resource handler requests. 41 | * Interceptors can be registered to apply to all requests or be limited 42 | * to a subset of URL patterns. 43 | */ 44 | @Override 45 | public void addInterceptors(InterceptorRegistry registry) { 46 | registry.addInterceptor(new IpHandlerInterceptor()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisJedisApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.context.annotation.EnableMBeanExport; 6 | import org.springframework.jmx.support.RegistrationPolicy; 7 | 8 | @SpringBootApplication 9 | @EnableMBeanExport(registration=RegistrationPolicy.IGNORE_EXISTING) 10 | public class RedisJedisApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(RedisJedisApplication.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/service/TestService.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.service; 2 | 3 | public interface TestService { 4 | 5 | String greetings(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/service/TestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.service; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; 4 | import com.giffing.bucket4j.spring.boot.starter.servlet.IpHandlerInterceptor; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.context.request.RequestAttributes; 7 | import org.springframework.web.context.request.RequestContextHolder; 8 | 9 | @Component 10 | public class TestServiceImpl implements TestService { 11 | 12 | private static final String name = "Horst"; 13 | 14 | @RateLimiting( 15 | name = "method_test", 16 | cacheKey = "@testServiceImpl.getRemoteAddr()", 17 | ratePerMethod = true, 18 | fallbackMethodName = "greetingsFallback" 19 | ) 20 | @Override 21 | public String greetings() { 22 | return String.format("Hello %s!", name); 23 | } 24 | 25 | @SuppressWarnings("unused") 26 | public String greetingsFallback() { 27 | return String.format("You are not welcome %s!", name); 28 | } 29 | 30 | @SuppressWarnings("unused") 31 | public String getRemoteAddr() { 32 | try { 33 | return (String) RequestContextHolder.currentRequestAttributes().getAttribute(IpHandlerInterceptor.IP, RequestAttributes.SCOPE_REQUEST); 34 | } catch (IllegalStateException e) { 35 | return "0.0.0.0"; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/servlet/IpHandlerInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.servlet; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import jakarta.servlet.http.HttpServletResponse; 5 | import org.springframework.web.context.request.RequestAttributes; 6 | import org.springframework.web.context.request.RequestContextHolder; 7 | import org.springframework.web.servlet.AsyncHandlerInterceptor; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | 10 | public class IpHandlerInterceptor implements HandlerInterceptor { 11 | 12 | public static final String IP = "ip"; 13 | 14 | /** 15 | * Interception point before the execution of a handler. Called after 16 | * HandlerMapping determined an appropriate handler object, but before 17 | * HandlerAdapter invokes the handler. 18 | *

DispatcherServlet processes a handler in an execution chain, consisting 19 | * of any number of interceptors, with the handler itself at the end. 20 | * With this method, each interceptor can decide to abort the execution chain, 21 | * typically sending an HTTP error or writing a custom response. 22 | *

Note: special considerations apply for asynchronous 23 | * request processing. For more details see 24 | * {@link AsyncHandlerInterceptor}. 25 | *

The default implementation returns {@code true}. 26 | * 27 | * @param request current HTTP request 28 | * @param response current HTTP response 29 | * @param handler chosen handler to execute, for type and/or instance evaluation 30 | * @return {@code true} if the execution chain should proceed with the 31 | * next interceptor or the handler itself. Else, DispatcherServlet assumes 32 | * that this interceptor has already dealt with the response itself. 33 | * @throws Exception in case of errors 34 | */ 35 | @Override 36 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 37 | 38 | RequestContextHolder.currentRequestAttributes().setAttribute(IP, RequestUtils.getIpFromRequest(request), RequestAttributes.SCOPE_REQUEST); 39 | 40 | return true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/java/com/giffing/bucket4j/spring/boot/starter/servlet/RequestUtils.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.servlet; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import org.springframework.util.StringUtils; 5 | 6 | public class RequestUtils { 7 | 8 | public static String getIpFromRequest(HttpServletRequest request) { 9 | var ip = request.getHeader("x-forwarded-for"); 10 | if (!StringUtils.hasText(ip)) { 11 | ip = request.getHeader("X-Forwarded-For"); 12 | } 13 | if (!StringUtils.hasText(ip)) { 14 | ip = request.getRemoteAddr(); 15 | } 16 | 17 | return ip; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | debug: false 2 | logging: 3 | level: 4 | com.giffing.bucket4j: debug 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | security: 11 | enabled: false 12 | 13 | bucket4j: 14 | enabled: true 15 | cache-to-use: redis-jedis 16 | filter-config-caching-enabled: true 17 | filter-config-cache-name: filterConfigCache 18 | filters: 19 | - cache-name: buckets_test 20 | major-version: 2 21 | url: ^(/hello).* 22 | id: filter1 23 | rate-limits: 24 | - tokens-inheritance-strategy: reset 25 | bandwidths: 26 | - capacity: 5 27 | time: 10 28 | unit: seconds 29 | refill-speed: interval 30 | - cache-name: buckets_test 31 | url: ^(/world).* 32 | id: filter2 33 | rate-limits: 34 | - tokens-inheritance-strategy: reset 35 | bandwidths: 36 | - capacity: 10 37 | time: 10 38 | unit: seconds 39 | refill-speed: interval 40 | methods: 41 | - name: method_test 42 | cache-name: greetings 43 | rate-limit: 44 | bandwidths: 45 | - capacity: 5 46 | time: 30 47 | unit: seconds 48 | refill-speed: interval 49 | default-metric-tags: 50 | - key: IP 51 | expression: "getRemoteAddr()" 52 | types: 53 | - REJECTED_COUNTER 54 | - CONSUMED_COUNTER 55 | - PARKED_COUNTER 56 | - INTERRUPTED_COUNTER 57 | - DELAYED_COUNTER 58 | default-method-metric-tags: 59 | - key: IP 60 | expression: "@testServiceImpl.getRemoteAddr()" 61 | types: 62 | - REJECTED_COUNTER 63 | - CONSUMED_COUNTER 64 | - PARKED_COUNTER 65 | - INTERRUPTED_COUNTER 66 | - DELAYED_COUNTER 67 | spring: 68 | main: 69 | allow-bean-definition-overriding: true 70 | data: 71 | redis: 72 | host: localhost 73 | port: 6379 74 | 75 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/JedisGreadyRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.JedisConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(JedisConfiguration.class) 14 | public class JedisGreadyRefillSpeedTest extends GreadyRefillSpeedTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/JedisIntervalRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.JedisConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(JedisConfiguration.class) 14 | public class JedisIntervalRefillSpeedTest extends IntervalRefillSpeedTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/servlet/JedisServletRateLimitTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.servlet; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.JedisConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(JedisConfiguration.class) 14 | public class JedisServletRateLimitTest extends ServletRateLimitTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-jedis/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | redis: 4 | host: localhost 5 | port: 6379 6 | 7 | -------------------------------------------------------------------------------- /examples/redis-lettuce/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/LettuceConfiguraiton.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import io.lettuce.core.RedisClient; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class LettuceConfiguraiton { 10 | 11 | @Bean 12 | public RedisClient redisClient(@Value("${spring.data.redis.port}") String port) { 13 | return RedisClient.create("redis://password@localhost:%s/".formatted(port)); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /examples/redis-lettuce/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisLettuceApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RedisLettuceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RedisLettuceApplication.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /examples/redis-lettuce/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | debug: false 2 | logging: 3 | level: 4 | com.giffing.bucket4j: debug 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | security: 11 | enabled: false 12 | 13 | bucket4j: 14 | enabled: true 15 | cache-to-use: redis-lettuce 16 | filter-config-caching-enabled: true 17 | filter-config-cache-name: filterConfigCache 18 | filters: 19 | - cache-name: buckets_test 20 | id: filter1 21 | filter-method: webflux 22 | url: ^(/hello).* 23 | rate-limits: 24 | - bandwidths: 25 | - capacity: 5 26 | time: 10 27 | unit: seconds 28 | refill-speed: interval 29 | - cache-name: buckets_test 30 | id: filter2 31 | filter-method: webflux 32 | url: ^(/world).* 33 | rate-limits: 34 | - bandwidths: 35 | - capacity: 10 36 | time: 10 37 | unit: seconds 38 | refill-speed: interval 39 | 40 | spring: 41 | main: 42 | allow-bean-definition-overriding: true 43 | data: 44 | redis: 45 | port: 6379 46 | -------------------------------------------------------------------------------- /examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/LettuceGreadyRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.LettuceConfiguraiton; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(LettuceConfiguraiton.class) 14 | public class LettuceGreadyRefillSpeedTest extends ReactiveGreadyRefillSpeedTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/LettuceIntervalRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.LettuceConfiguraiton; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(LettuceConfiguraiton.class) 14 | public class LettuceIntervalRefillSpeedTest extends ReactiveIntervalRefillSpeedTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-lettuce/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/LettuceServletRateLimitTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.LettuceConfiguraiton; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.context.annotation.Import; 7 | import org.springframework.test.context.DynamicPropertyRegistry; 8 | import org.springframework.test.context.DynamicPropertySource; 9 | import org.testcontainers.containers.GenericContainer; 10 | import org.testcontainers.junit.jupiter.Container; 11 | import org.testcontainers.junit.jupiter.Testcontainers; 12 | import org.testcontainers.utility.DockerImageName; 13 | 14 | @Testcontainers 15 | @Import(LettuceConfiguraiton.class) 16 | @Disabled("Test ist not running on github - TODO") 17 | public class LettuceServletRateLimitTest extends ReactiveRateLimitTest { 18 | 19 | @Container 20 | static final GenericContainer redis = 21 | new GenericContainer(DockerImageName.parse("redis:7")) 22 | .withExposedPorts(6379); 23 | 24 | @DynamicPropertySource 25 | static void redisProperties(DynamicPropertyRegistry registry) { 26 | registry.add("spring.data.redis.host", () -> redis.getHost()); 27 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /examples/redis-lettuce/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | allow-bean-definition-overriding: true 4 | data: 5 | redis: 6 | port: 6379 7 | -------------------------------------------------------------------------------- /examples/redis-redisson/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/DebugMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 4 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 5 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @Component 13 | @Slf4j 14 | public class DebugMetricHandler implements MetricHandler { 15 | 16 | @Override 17 | public void handle(MetricType type, String name, long tokens, List tags) { 18 | log.info(String.format("type: %s; name: %s; tags: %s; tokens: %s", 19 | type, 20 | name, 21 | tags 22 | .stream() 23 | .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) 24 | .collect(Collectors.joining(",")), 25 | tokens)); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedisRedissonApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class RedisRedissonApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(RedisRedissonApplication.class, args); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /examples/redis-redisson/src/main/java/com/giffing/bucket4j/spring/boot/starter/RedissonConfiguraiton.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter; 2 | 3 | import org.redisson.Redisson; 4 | import org.redisson.command.CommandAsyncExecutor; 5 | import org.redisson.config.Config; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class RedissonConfiguraiton { 12 | 13 | @Bean 14 | CommandAsyncExecutor getCommandAsyncExecutor(@Value("${spring.data.redis.host}") String host, 15 | @Value("${spring.data.redis.port}") String port) { 16 | var address = "redis://%s:%s".formatted(host, port); 17 | 18 | var config = new Config(); 19 | config.useSingleServer() 20 | .setAddress(address) 21 | .setRetryAttempts(5); 22 | return ((Redisson)Redisson.create(config)).getCommandExecutor(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/redis-redisson/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | com.giffing.bucket4j: debug 4 | management: 5 | endpoints: 6 | web: 7 | exposure: 8 | include: "*" 9 | security: 10 | enabled: false 11 | 12 | bucket4j: 13 | enabled: true 14 | cache-to-use: redis-redisson 15 | filter-config-caching-enabled: true 16 | filter-config-cache-name: filterConfigCache 17 | filters: 18 | - id: filter1 19 | major-version: 1 20 | cache-name: buckets_test 21 | filter-method: webflux 22 | url: ^(/hello).* 23 | rate-limits: 24 | - tokens-inheritance-strategy: reset 25 | bandwidths: 26 | - id: bandwidth1 27 | capacity: 5 28 | time: 10 29 | unit: seconds 30 | refill-speed: interval 31 | - id: filter2 32 | major-version: 1 33 | cache-name: buckets_test 34 | filter-method: webflux 35 | url: ^(/world).* 36 | rate-limits: 37 | - tokens-inheritance-strategy: reset 38 | bandwidths: 39 | - id: bandwidth3 40 | capacity: 10 41 | time: 10 42 | unit: seconds 43 | refill-speed: interval 44 | 45 | spring: 46 | main: 47 | allow-bean-definition-overriding: true 48 | data: 49 | redis: 50 | host: localhost 51 | port: 6379 52 | -------------------------------------------------------------------------------- /examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/RedissonGreadyRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.RedissonConfiguraiton; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(RedissonConfiguraiton.class) 14 | public class RedissonGreadyRefillSpeedTest extends ReactiveGreadyRefillSpeedTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/RedissonIntervalRefillSpeedTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.RedissonConfiguraiton; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(RedissonConfiguraiton.class) 14 | public class RedissonIntervalRefillSpeedTest extends ReactiveIntervalRefillSpeedTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-redisson/src/test/java/com/giffing/bucket4j/spring/boot/starter/general/tests/filter/reactive/RedissonServletRateLimitTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.RedissonConfiguraiton; 4 | import org.springframework.context.annotation.Import; 5 | import org.springframework.test.context.DynamicPropertyRegistry; 6 | import org.springframework.test.context.DynamicPropertySource; 7 | import org.testcontainers.containers.GenericContainer; 8 | import org.testcontainers.junit.jupiter.Container; 9 | import org.testcontainers.junit.jupiter.Testcontainers; 10 | import org.testcontainers.utility.DockerImageName; 11 | 12 | @Testcontainers 13 | @Import(RedissonConfiguraiton.class) 14 | public class RedissonServletRateLimitTest extends ReactiveRateLimitTest { 15 | 16 | @Container 17 | static final GenericContainer redis = 18 | new GenericContainer(DockerImageName.parse("redis:7")) 19 | .withExposedPorts(6379); 20 | 21 | @DynamicPropertySource 22 | static void redisProperties(DynamicPropertyRegistry registry) { 23 | registry.add("spring.data.redis.host", () -> redis.getHost()); 24 | registry.add("spring.data.redis.port", () -> redis.getFirstMappedPort()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/redis-redisson/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | allow-bean-definition-overriding: true 4 | data: 5 | redis: 6 | host: localhost 7 | port: 6379 8 | -------------------------------------------------------------------------------- /examples/webflux-infinispan/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/webflux/DebugMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.webflux; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 10 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 11 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 12 | 13 | @Component 14 | @Slf4j 15 | public class DebugMetricHandler implements MetricHandler { 16 | 17 | @Override 18 | public void handle(MetricType type, String name, long tokens, List tags) { 19 | log.info("type: {}; name: {}; tags: {}", 20 | type, 21 | name, 22 | tags 23 | .stream() 24 | .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) 25 | .collect(Collectors.joining(","))); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/webflux/WebfluxInfinispanApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.webflux; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class WebfluxInfinispanApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(WebfluxInfinispanApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | com.giffing.bucket4j.spring.boot.starter: debug 4 | 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | spring: 11 | cache: 12 | type: infinispan 13 | infinispan: 14 | embedded: 15 | config-xml: infinispan.xml 16 | bucket4j: 17 | enabled: true 18 | filter-config-caching-enabled: true 19 | filter-config-cache-name: filterConfigCache 20 | filters: 21 | - id: filter1 22 | cache-name: buckets 23 | filter-method: webflux 24 | url: .* 25 | http-content-type: application/json;charset=UTF-8 26 | http-response-body: '{ "name": "hello"}' 27 | http-response-headers: 28 | HELLO: WORLD 29 | filter-order: 1 30 | rate-limits: 31 | - bandwidths: 32 | - capacity: 5 33 | time: 10 34 | unit: seconds 35 | -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/main/resources/infinispan.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/webflux/WebfluxGeneralSuiteTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.webflux; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive.WebfluxTestSuite; 4 | import org.junit.platform.suite.api.SelectClasses; 5 | import org.junit.platform.suite.api.Suite; 6 | 7 | @Suite 8 | @SelectClasses({ 9 | WebfluxTestSuite.class 10 | }) 11 | public class WebfluxGeneralSuiteTest { 12 | } 13 | -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/test/resources/application-webflux-infinispan.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | type: infinispan 4 | infinispan: 5 | embedded: 6 | config-xml: infinispan.xml 7 | bucket4j: 8 | enabled: true 9 | filter-config-caching-enabled: true 10 | filter-config-cache-name: filterConfigCache 11 | filters: 12 | - id: filter1 13 | cache-name: buckets_test 14 | filter-method: webflux 15 | url: ^(/hello).* 16 | rate-limits: 17 | - bandwidths: 18 | - capacity: 5 19 | time: 10 20 | unit: seconds 21 | - id: filter2 22 | cache-name: buckets_test 23 | filter-method: webflux 24 | url: ^(/world).* 25 | rate-limits: 26 | - bandwidths: 27 | - capacity: 10 28 | time: 10 29 | unit: seconds -------------------------------------------------------------------------------- /examples/webflux-infinispan/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | type: infinispan 4 | infinispan: 5 | embedded: 6 | config-xml: infinispan.xml -------------------------------------------------------------------------------- /examples/webflux/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.settings/ 3 | .classpath 4 | .project 5 | .idea/ 6 | *.iml 7 | .factorypath 8 | .apt_generated 9 | .springBeans -------------------------------------------------------------------------------- /examples/webflux/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/webflux/DebugMetricHandler.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.webflux; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricHandler; 10 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricTagResult; 11 | import com.giffing.bucket4j.spring.boot.starter.context.metrics.MetricType; 12 | 13 | @Component 14 | @Slf4j 15 | public class DebugMetricHandler implements MetricHandler { 16 | 17 | @Override 18 | public void handle(MetricType type, String name, long tokens, List tags) { 19 | log.info(String.format("type: %s; name: %s; tags: %s", 20 | type, 21 | name, 22 | tags 23 | .stream() 24 | .map(mtr -> mtr.getKey() + ":" + mtr.getValue()) 25 | .collect(Collectors.joining(",")))); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/webflux/src/main/java/com/giffing/bucket4j/spring/boot/starter/examples/webflux/WebfluxApplication.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.webflux; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class WebfluxApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(WebfluxApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/webflux/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | level: 3 | com.giffing.bucket4j.spring.boot.starter: debug 4 | management: 5 | endpoints: 6 | web: 7 | exposure: 8 | include: "*" 9 | spring: 10 | cache: 11 | type: hazelcast 12 | bucket4j: 13 | enabled: true 14 | filter-config-caching-enabled: true 15 | filter-config-cache-name: filterConfigCache 16 | filters: 17 | - cache-name: buckets 18 | filter-method: webflux 19 | url: .* 20 | http-content-type: application/json;charset=UTF-8 21 | http-response-body: '{ "name": "hello"}' 22 | http-response-headers: 23 | HELLO: WORLD 24 | filter-order: 1 25 | rate-limits: 26 | - execute-predicates: 27 | - name: PATH=/hell** 28 | - name: METHOD=GET 29 | - name: HEADER=User-Agent,.* 30 | bandwidths: 31 | - capacity: 5 32 | time: 10 33 | unit: seconds 34 | -------------------------------------------------------------------------------- /examples/webflux/src/main/resources/hazelcast.yaml: -------------------------------------------------------------------------------- 1 | hazelcast: 2 | network: 3 | join: 4 | tcp-ip: 5 | enabled: true 6 | interface: 127.0.0.1 -------------------------------------------------------------------------------- /examples/webflux/src/test/java/com/giffing/bucket4j/spring/boot/starter/examples/webflux/WebfluxGeneralSuiteTest.java: -------------------------------------------------------------------------------- 1 | package com.giffing.bucket4j.spring.boot.starter.examples.webflux; 2 | 3 | import com.giffing.bucket4j.spring.boot.starter.general.tests.filter.reactive.WebfluxTestSuite; 4 | import org.junit.platform.suite.api.SelectClasses; 5 | import org.junit.platform.suite.api.Suite; 6 | 7 | @Suite 8 | @SelectClasses({ 9 | WebfluxTestSuite.class 10 | }) 11 | public class WebfluxGeneralSuiteTest { 12 | } 13 | -------------------------------------------------------------------------------- /examples/webflux/src/test/resources/application-webflux.yml: -------------------------------------------------------------------------------- 1 | bucket4j: 2 | enabled: true 3 | filter-config-caching-enabled: true 4 | filter-config-cache-name: filterConfigCache 5 | filters: 6 | - id: filter1 7 | cache-name: buckets_test 8 | filter-method: webflux 9 | url: ^(/hello).* 10 | rate-limits: 11 | - bandwidths: 12 | - capacity: 5 13 | time: 10 14 | unit: seconds 15 | - id: filter2 16 | cache-name: buckets_test 17 | filter-method: webflux 18 | url: ^(/world).* 19 | rate-limits: 20 | - bandwidths: 21 | - capacity: 10 22 | time: 10 23 | unit: seconds -------------------------------------------------------------------------------- /examples/webflux/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cache: 3 | type: hazelcast -------------------------------------------------------------------------------- /src/main/doc/plantuml/post_execution_condition.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | User -> Bucket4jFilter: request 4 | 5 | box "Webserver" #f5e4e4 6 | 7 | Bucket4jFilter -> Bucket4jFilter : estimate_remaining_tokens 8 | participant Bucket4jFilter 9 | participant SpringSecurityFilter 10 | alt 1 token available 11 | 12 | note right of Bucket4jFilter: There is one token available. \nThe request will not be aborted 13 | Bucket4jFilter -> SpringSecurityFilter : request 14 | SpringSecurityFilter -> SpringSecurityFilter : authenticate 15 | SpringSecurityFilter -> Bucket4jFilter : response(401) 16 | alt HTTP Response Status == 401 17 | note right of Bucket4jFilter: The token will only be consumed\n if the HTTP Status is 401\n 18 | Bucket4jFilter -> Bucket4jFilter : consume_token 19 | end 20 | Bucket4jFilter -> User : response(401) 21 | 22 | else 0 token available 23 | note right of Bucket4jFilter: The token was consumed \nbecause of the HTTP Response Status 401 24 | Bucket4jFilter -> Bucket4jFilter : reject request 25 | Bucket4jFilter -> User : response\n(429 Too Many Requests) 26 | end 27 | 28 | 29 | 30 | 31 | 32 | end box 33 | 34 | 35 | @enduml -------------------------------------------------------------------------------- /src/main/doc/plantuml/post_execution_condition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarcGiffing/bucket4j-spring-boot-starter/cd19210ee2d6ee8cd225640c9c144df4e3465d59/src/main/doc/plantuml/post_execution_condition.png --------------------------------------------------------------------------------