diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/Condition.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/Condition.java index 05d5826e..346afcfb 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/Condition.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/Condition.java @@ -2,7 +2,7 @@ /** - * The skip condition is used to skip or execute a rate limit check. + * This condition is used to skip or execute a rate limit check. */ @FunctionalInterface public interface Condition { diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExecutePredicateDefinition.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExecutePredicateDefinition.java index 6f3b0673..0e1bed52 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExecutePredicateDefinition.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/ExecutePredicateDefinition.java @@ -39,7 +39,7 @@ public ExecutePredicateDefinition(String name) { "Unable to parse ExecutePredicateDefinition text '" + name + "'" + ", must be of the form name=value"); } this.name = name.substring(0, eqIdx); - var result = name.substring(eqIdx + 1, name.length()); + var result = name.substring(eqIdx + 1); this.args.put(SIMPLE_CONFIG_KEY, result); log.debug("execute-predicate-simple-config;name:{};value:{}", this.name, result); } diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/KeyFilter.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/KeyFilter.java index 5a1d926b..5ae4d84e 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/KeyFilter.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/KeyFilter.java @@ -2,7 +2,6 @@ /** * Functional interface to retrieve the Bucket4j key. The key is used to identify the Bucket4j storage. - * */ @FunctionalInterface public interface KeyFilter { diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/PostRateLimitCheck.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/PostRateLimitCheck.java index a00b9df6..405dce34 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/PostRateLimitCheck.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/PostRateLimitCheck.java @@ -4,7 +4,6 @@ /** * Used to check if the rate limit should be performed independently from the servlet|webflux|gateway request filter - * */ @FunctionalInterface public interface PostRateLimitCheck { diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitCheck.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitCheck.java index b82213c6..4e9ac935 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitCheck.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitCheck.java @@ -4,7 +4,7 @@ import com.giffing.bucket4j.spring.boot.starter.context.properties.RateLimit; /** - * Used to check if the rate limit should be performed independently from the servlet|webflux|gateway request filter + * Used to check if the rate limit should be performed independently of the servlet|webflux|gateway request filter */ @FunctionalInterface public interface RateLimitCheck { diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResult.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResult.java index b06b12a3..d8066a29 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResult.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResult.java @@ -4,22 +4,42 @@ import lombok.Data; import lombok.NonNull; +/** + * This data class holds the information of a rate limit check. + */ @Data @Builder public class RateLimitResult { + /** + * If the request was only for estimation without consuming any tokens. + */ @NonNull private final boolean estimation; + /** + * The tokens that are consumed. + *

+ * If {@link #estimation} is true no tokens are consumed. + */ @NonNull private final boolean consumed; + /** + * The number of tokens that remains until rate limit is executed. + */ @NonNull private final long remainingTokens; + /** + * The time in nanoseconds until the next tokens will be refilled. + */ @NonNull private final long nanosToWaitForRefill; + /** + * The time in nanoseconds until the tokens are refilled to its maximum. + */ @NonNull private final long nanosToWaitForReset; } diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResultWrapper.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResultWrapper.java index 919984cd..fa2c1295 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResultWrapper.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RateLimitResultWrapper.java @@ -4,20 +4,24 @@ import java.util.concurrent.CompletableFuture; - +/** + * A wrapper class to distinguish the {@link RateLimiting} result between a synchronous and asynchronous call. + *

+ * If possible we should get rid of it... + */ @Data public class RateLimitResultWrapper { - private RateLimitResult rateLimitResult; - - private CompletableFuture rateLimitResultCompletableFuture; - - public RateLimitResultWrapper(RateLimitResult rateLimitResult) { - this.rateLimitResult = rateLimitResult; - } - - public RateLimitResultWrapper(CompletableFuture rateLimitResultCompletableFuture) { - this.rateLimitResultCompletableFuture = rateLimitResultCompletableFuture; - } + private RateLimitResult rateLimitResult; + + private CompletableFuture rateLimitResultCompletableFuture; + + public RateLimitResultWrapper(RateLimitResult rateLimitResult) { + this.rateLimitResult = rateLimitResult; + } + + public RateLimitResultWrapper(CompletableFuture rateLimitResultCompletableFuture) { + this.rateLimitResultCompletableFuture = rateLimitResultCompletableFuture; + } } diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RefillSpeed.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RefillSpeed.java index cad64572..a28a6f2b 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RefillSpeed.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/RefillSpeed.java @@ -2,14 +2,18 @@ public enum RefillSpeed { - /** - * Greedily regenerates tokens - */ - GREEDY, - - /** - * Regenerates tokens in an interval manner - */ - INTERVAL, - + /** + * Greedily regenerates tokens. + *

+ * The tokens are refilled as soon as possible. + */ + GREEDY, + + /** + * Regenerates tokens in an interval manner. + *

+ * The tokens refilled on the specific defined interval. + */ + INTERVAL, + } diff --git a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/BandWidth.java b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/BandWidth.java index 8c329431..bfa9b23e 100644 --- a/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/BandWidth.java +++ b/bucket4j-spring-boot-starter-context/src/main/java/com/giffing/bucket4j/spring/boot/starter/context/properties/BandWidth.java @@ -12,7 +12,7 @@ import java.time.temporal.ChronoUnit; /** - * Configures the rate of data which should be transfered + * Configures the rate of data which should be transferred. * */ @Data diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/aspect/RateLimitAspect.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/aspect/RateLimitAspect.java index d4258179..319a9572 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/aspect/RateLimitAspect.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/aspect/RateLimitAspect.java @@ -23,6 +23,10 @@ import java.util.List; import java.util.Map; +/** + * Creates an Aspect around methods annotated with @{@link RateLimiting}. It prevents the execution of the method + * if rate limit should be executed. + */ @Aspect @Component @RequiredArgsConstructor diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java index 4395441c..ace8c7de 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastReactiveBucket4jCacheConfiguration.java @@ -3,6 +3,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.JCacheBucket4jConfiguration; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnAsynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; @@ -20,7 +21,7 @@ * to access the {@link HazelcastInstance} to retrieve the cache. */ @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@ConditionalOnAsynchronousPropertyCondition @ConditionalOnClass({HazelcastInstance.class}) @ConditionalOnBean(HazelcastInstance.class) @ConditionalOnCache("hazelcast-reactive") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java index 90c75a94..a4581c34 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/hazelcast/HazelcastSpringBucket4jCacheConfiguration.java @@ -6,6 +6,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.JCacheBucket4jConfiguration; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnSynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; import com.hazelcast.core.HazelcastInstance; @@ -22,7 +23,7 @@ * to access the {@link HazelcastInstance} to retrieve the cache. */ @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnSynchronousPropertyCondition @ConditionalOnClass({HazelcastCacheManager.class}) @ConditionalOnBean(HazelcastCacheManager.class) @ConditionalOnCache("hazelcast-spring") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteBucket4jCacheConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteBucket4jCacheConfiguration.java index 99362c67..c6854032 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteBucket4jCacheConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/ignite/IgniteBucket4jCacheConfiguration.java @@ -2,6 +2,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnAsynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; @@ -13,7 +14,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@ConditionalOnAsynchronousPropertyCondition @ConditionalOnClass({ Ignite.class }) @ConditionalOnBean(Ignite.class) @ConditionalOnCache("ignite") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanBucket4jCacheConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanBucket4jCacheConfiguration.java index 85792775..07771bc1 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanBucket4jCacheConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/infinispan/InfinispanBucket4jCacheConfiguration.java @@ -2,6 +2,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnAsynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; @@ -13,7 +14,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@ConditionalOnAsynchronousPropertyCondition @ConditionalOnClass({CacheContainer.class}) @ConditionalOnBean(CacheContainer.class) @ConditionalOnMissingBean(AsyncCacheResolver.class) diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/InfinispanJCacheBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/InfinispanJCacheBucket4jConfiguration.java index 0813a57a..babd0a36 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/InfinispanJCacheBucket4jConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/InfinispanJCacheBucket4jConfiguration.java @@ -7,6 +7,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.infinispan.InfinispanCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnSynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; import org.infinispan.manager.CacheContainer; @@ -23,7 +24,7 @@ * bucket4j. See {@link InfinispanCacheResolver} for more informations. */ @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnSynchronousPropertyCondition @ConditionalOnClass({CacheContainer.class, Caching.class, JCacheCacheManager.class}) @ConditionalOnBean(CacheContainer.class) @ConditionalOnCache("jcache-ignite") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheBucket4jConfiguration.java index 86f42fd5..a14ebb43 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheBucket4jConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/jcache/JCacheBucket4jConfiguration.java @@ -3,6 +3,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnSynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; import org.springframework.boot.autoconfigure.condition.*; @@ -15,7 +16,7 @@ import javax.cache.Caching; @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnSynchronousPropertyCondition @ConditionalOnClass({Caching.class, JCacheCacheManager.class}) @ConditionalOnBean(CacheManager.class) @ConditionalOnCache("jcache") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/jedis/JedisBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/jedis/JedisBucket4jConfiguration.java index 167da37e..5445d7da 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/jedis/JedisBucket4jConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/jedis/JedisBucket4jConfiguration.java @@ -4,6 +4,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnSynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JConfiguration; import io.github.bucket4j.redis.jedis.cas.JedisBasedProxyManager.JedisBasedProxyManagerBuilder; @@ -14,7 +15,7 @@ import redis.clients.jedis.JedisPool; @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +@ConditionalOnSynchronousPropertyCondition @ConditionalOnClass(JedisBasedProxyManagerBuilder.class) @ConditionalOnBean(JedisPool.class) @ConditionalOnCache("redis-jedis") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/lettuce/LettuceBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/lettuce/LettuceBucket4jConfiguration.java index 6840dd9f..271e233b 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/lettuce/LettuceBucket4jConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/lettuce/LettuceBucket4jConfiguration.java @@ -2,6 +2,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnAsynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; @@ -14,7 +15,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@ConditionalOnAsynchronousPropertyCondition @ConditionalOnClass(LettuceBasedProxyManager.class) @ConditionalOnBean(RedisClient.class) @ConditionalOnCache("redis-lettuce") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java index dee2944b..afaa7038 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/redisson/RedissonBucket4jConfiguration.java @@ -2,6 +2,7 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.AsyncCacheResolver; import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheManager; +import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnAsynchronousPropertyCondition; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnCache; import com.giffing.bucket4j.spring.boot.starter.config.condition.ConditionalOnFilterConfigCacheEnabled; import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; @@ -17,7 +18,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +@ConditionalOnAsynchronousPropertyCondition @ConditionalOnClass(RedissonBasedProxyManager.class) @ConditionalOnBean(CommandAsyncExecutor.class) @ConditionalOnCache("redis-redisson") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnAsynchronousPropertyCondition.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnAsynchronousPropertyCondition.java new file mode 100644 index 00000000..2552dd69 --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnAsynchronousPropertyCondition.java @@ -0,0 +1,16 @@ +package com.giffing.bucket4j.spring.boot.starter.config.condition; + +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + * {@link Conditional @Conditional} that only matches when in the Bucket4j properties + * asynchronous configuration exists. E.g. there are reactive filters registered (WEBFLUX, GATEWAY). + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnAsynchronousPropertyCondition.class) +public @interface ConditionalOnAsynchronousPropertyCondition { +} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnBucket4jEnabled.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnBucket4jEnabled.java index 071683f5..f1ed8199 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnBucket4jEnabled.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnBucket4jEnabled.java @@ -2,14 +2,22 @@ import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * {@link Conditional @Conditional} that matches under the following conditions. + *

    + *
  • The 'bucket4j.enabled' is not set.
  • + *
  • The 'bucket4j.enabled' is set to true.
  • + *
+ */ @Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, value = { "enabled" }, matchIfMissing = true) +@Target({ElementType.TYPE, ElementType.METHOD}) +@ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, value = {"enabled"}, matchIfMissing = true) public @interface ConditionalOnBucket4jEnabled { } diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnCache.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnCache.java index f1093f1b..32511b37 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnCache.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnCache.java @@ -2,6 +2,7 @@ import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.ElementType; @@ -9,6 +10,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * {@link Conditional @Conditional} that matches under the following conditions. + *
    + *
  • The 'bucket4j.cache-to-use' is not set.
  • + *
  • The 'bucket4j.cache-to-use' property matches the given {@link #value()}.
  • + *
+ */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "cache-to-use", matchIfMissing = true) diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnFilterConfigCacheEnabled.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnFilterConfigCacheEnabled.java index a291fba3..445e911b 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnFilterConfigCacheEnabled.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnFilterConfigCacheEnabled.java @@ -2,12 +2,19 @@ import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Conditional; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * {@link Conditional @Conditional} that matches under the following conditions. + *
    + *
  • The 'bucket4j.filter-config-caching-enabled' is set to true}.
  • + *
+ */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, name = "filter-config-caching-enabled", havingValue = "true") diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnSynchronousPropertyCondition.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnSynchronousPropertyCondition.java new file mode 100644 index 00000000..15b99dcd --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/ConditionalOnSynchronousPropertyCondition.java @@ -0,0 +1,18 @@ +package com.giffing.bucket4j.spring.boot.starter.config.condition; + +import com.giffing.bucket4j.spring.boot.starter.context.RateLimiting; +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + * {@link Conditional @Conditional} that only matches when in the Bucket4j properties + * a synchronous configuration exists. E.g. the is a method configuration for the @{@link RateLimiting} annotation + * or a Servlet Filter configuration exists. + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnSynchronousPropertyCondition.class) +public @interface ConditionalOnSynchronousPropertyCondition { +} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnAsynchronousPropertyCondition.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnAsynchronousPropertyCondition.java new file mode 100644 index 00000000..6a13b4a3 --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnAsynchronousPropertyCondition.java @@ -0,0 +1,26 @@ +package com.giffing.bucket4j.spring.boot.starter.config.condition; + +import com.giffing.bucket4j.spring.boot.starter.context.FilterMethod; +import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +import java.util.List; + +public class OnAsynchronousPropertyCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + var bucket4jProperties = Binder.get(context.getEnvironment()).bind("bucket4j", Bucket4JBootProperties.class).orElse(null); + var reactiveFilterConfigurationExists = bucket4jProperties.getFilters() + .stream() + .anyMatch(x -> List.of(FilterMethod.WEBFLUX, FilterMethod.GATEWAY).contains(x.getFilterMethod())); + if (reactiveFilterConfigurationExists) { + return ConditionOutcome.match("@ConditionalOnAsynchronousPropertyCondition Found reactive filter"); + } else { + return ConditionOutcome.noMatch("@ConditionalOnAsynchronousPropertyCondition No filter configuration with FilterMethod.WEBFLUX org FilterMethod.GATEWAY configured"); + } + } +} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnSynchronousPropertyCondition.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnSynchronousPropertyCondition.java new file mode 100644 index 00000000..8127b6cb --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/condition/OnSynchronousPropertyCondition.java @@ -0,0 +1,23 @@ +package com.giffing.bucket4j.spring.boot.starter.config.condition; + +import com.giffing.bucket4j.spring.boot.starter.context.FilterMethod; +import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class OnSynchronousPropertyCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + var bucket4jProperties = Binder.get(context.getEnvironment()).bind("bucket4j", Bucket4JBootProperties.class).orElse(null); + var methodConfigurationExists = !bucket4jProperties.getMethods().isEmpty(); + var servletConfigurationExist = bucket4jProperties.getFilters().stream().anyMatch(x -> x.getFilterMethod().equals(FilterMethod.SERVLET)); + if (methodConfigurationExists || servletConfigurationExist) { + return ConditionOutcome.match("@ConditionalOnSynchronPropertyCondition Found filter method and/or servlet configuration"); + } else { + return ConditionOutcome.noMatch("@ConditionalOnSynchronPropertyCondition No method configuration or filter configuration with FilterMethod.SERVLET configured"); + } + } +} diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/failures/RateLimitConfigurationStartupFailuresTest.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/failures/RateLimitConfigurationStartupFailuresTest.java index ab8d8e49..28a32b37 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/failures/RateLimitConfigurationStartupFailuresTest.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/failures/RateLimitConfigurationStartupFailuresTest.java @@ -34,6 +34,11 @@ private static Stream invalidParameter() { ); } + /** + * This test ensures that a Spring Expression with a method parameter that does not exist will throw a + * {@link RateLimitUnknownParameterException} exception and stops the application to start. + * The test is executed for execute, skip and cache-Key expressions. + */ @ParameterizedTest @MethodSource("invalidParameter") @DirtiesContext @@ -53,9 +58,13 @@ public void assert_startup_failure_when_execute_expression_has_invalid_method_pa assertEquals(parameters, String.join(",", unknownParameterException.getMethodParameter())); } + /** + * Asserts a {@link RateLimitingMethodNameNotConfiguredException} when the configuration name in the @{@link RateLimiting#name()} + * has no property reference 'bucket4j.methods[x].name'. + */ @Test @DirtiesContext - public void assert_startup_failure_when_execute_expression_has_invalid_method_parameter() { + public void assert_startup_failure_when_cache_name_in_annotation_does_not_exist() { SpringApplication springApplication = new SpringApplication(MyInternalApplication.class); springApplication.setAdditionalProfiles("invalidMethodName"); Properties properties = getValidBucket4jProperties(); @@ -67,6 +76,10 @@ public void assert_startup_failure_when_execute_expression_has_invalid_method_pa assertEquals("testInvalidMethodName", methodNameNotConfiguredException.getMethodName()); } + /** + * Asserts a {@link RateLimitingFallbackMethodNotFoundException} then the fallback method name in + * {@link RateLimiting#fallbackMethodName()} does not exists in the same class. + */ @Test @DirtiesContext public void assert_startup_failure_when_fallback_method_not_exists() { @@ -81,6 +94,10 @@ public void assert_startup_failure_when_fallback_method_not_exists() { assertEquals("testFallbackMethodNotExists", exception.getMethodName()); } + /** + * Asserts a {@link RateLimitingMultipleFallbackMethodsFoundException} when multiple {@link RateLimiting#fallbackMethodName()}s + * found in the same class. Only one is allowed. + */ @Test @DirtiesContext public void assert_startup_failure_when_multiple_fallback_method_exists() { @@ -95,6 +112,10 @@ public void assert_startup_failure_when_multiple_fallback_method_exists() { assertEquals("testMultipleFallbackMethods", exception.getMethodName()); } + /** + * Asserts a {@link RateLimitingFallbackReturnTypesMismatchException} when the return type of the fallback method + * does not have the same signature of the rate limit method. + */ @Test @DirtiesContext public void assert_startup_failure_when_return_type_from_fallback_method_differs() { @@ -111,6 +132,10 @@ public void assert_startup_failure_when_return_type_from_fallback_method_differs assertEquals("public final class java.lang.Integer", exception.getFallbackMethodReturnType()); } + /** + * Asserts a {@link RateLimitingFallbackMethodParameterMismatchException} when the parameters of the {@link RateLimiting#fallbackMethodName()} + * does not have the same count and signature as the rate limit method. + */ @Test @DirtiesContext public void assert_startup_failure_when_parameters_from_fallback_method_differs() { @@ -193,7 +218,6 @@ public InvalidFallbackMethodParameter invalidFallbackMethodParameter() { } - public static void main(String[] args) { SpringApplication.run(com.giffing.bucket4j.spring.boot.starter.general.tests.method.method.MethodTestApplication.class, args); } @@ -253,11 +277,11 @@ private static class MultipleFallbackMethods { public void testMultipleFallbackMethods(String cacheKey) { } - public void myFallbackMethod(String cacheKey){ + public void myFallbackMethod(String cacheKey) { } - public void myFallbackMethod(String cacheKey, String otherParameter){ + public void myFallbackMethod(String cacheKey, String otherParameter) { } } diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/ClassLevelTestService.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/ClassLevelTestService.java index 7ede0d3e..0c318810 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/ClassLevelTestService.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/ClassLevelTestService.java @@ -5,6 +5,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +/** + * Used to test a @{@link RateLimiting} annotation on class level. + */ @Component @Slf4j @RateLimiting(name = "default") @@ -14,6 +17,9 @@ public void notAnnotatedMethod() { log.info("Method notAnnotatedMethod"); } + /** + * Method should be ignored from rate limiting. + */ @IgnoreRateLimiting public void ignoreMethod() { log.info("Method ignoreMethod"); diff --git a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/IgnoreOnClassLevelTestService.java b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/IgnoreOnClassLevelTestService.java index 0bf9d746..32978a8d 100644 --- a/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/IgnoreOnClassLevelTestService.java +++ b/examples/general-tests/src/main/java/com/giffing/bucket4j/spring/boot/starter/general/tests/method/method/IgnoreOnClassLevelTestService.java @@ -5,6 +5,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +/** + * No rate limiting should be executed on any method if the class is annotated with @{@link IgnoreRateLimiting} + */ @Component @Slf4j @IgnoreRateLimiting