Skip to content

Commit

Permalink
Support for Method level @ratelimiting annoation #250
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcGiffing committed Mar 7, 2024
1 parent ae2a8a4 commit 206c6cf
Show file tree
Hide file tree
Showing 30 changed files with 888 additions and 555 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package com.giffing.bucket4j.spring.boot.starter.context;


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 from the servlet|webflux|gateway request filter
*/
@FunctionalInterface
public interface RateLimitCheck<R> {

/**
* @param request the request information object
*
* @return null if no rate limit should be performed. (maybe skipped or shouldn't be executed).
*/
RateLimitResultWrapper rateLimit(R request);
/**
* @param request the request information object
* @param mainRateLimit overwrites the rate limit configuration from the properties
* @return null if no rate limit should be performed. (maybe skipped or shouldn't be executed).
*/
RateLimitResultWrapper rateLimit(R request, RateLimit mainRateLimit);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.giffing.bucket4j.spring.boot.starter.context;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimiting {

String name();

String cacheKey() default "";

String executeCondition() default "";

String skipCondition() default "";

String fallbackMethodName() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class Bucket4JBootProperties {
*/
private String cacheToUse;

@Valid
private List<MethodProperties> methods = new ArrayList<>();

private boolean filterConfigCachingEnabled = false;

/**
Expand All @@ -51,6 +54,8 @@ public class Bucket4JBootProperties {
@NotBlank
private String filterConfigCacheName = "filterConfigCache";



@Valid
private List<Bucket4JConfiguration> filters = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.giffing.bucket4j.spring.boot.starter.context.properties;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class MethodProperties {

@NotBlank
private String name;

@NotBlank
private String cacheName;

@NotNull
private RateLimit rateLimit;

}
Original file line number Diff line number Diff line change
@@ -1,67 +1,113 @@
package com.giffing.bucket4j.spring.boot.starter.context.properties;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.giffing.bucket4j.spring.boot.starter.context.ExecutePredicateDefinition;
import com.giffing.bucket4j.spring.boot.starter.context.constraintvalidations.ValidBandWidthIds;

import jakarta.validation.Valid;
import jakarta.validation.constraints.*;

import io.github.bucket4j.TokensInheritanceStrategy;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Data
@ValidBandWidthIds
public class RateLimit implements Serializable {

/**
* SpEl condition to check if the rate limit should be executed. If null there is no check.
*/
private String executeCondition;

/**
* TODO comment
*/
private String postExecuteCondition;

@Valid
private List<ExecutePredicateDefinition> executePredicates = new ArrayList<>();

/**
* SpEl condition to check if the rate-limit should apply. If null there is no check.
*/
private String skipCondition;

@Valid
private List<ExecutePredicateDefinition> skipPredicates = new ArrayList<>();

/**
* SPEL expression to dynamic evaluate filter key
*/
@NotBlank
private String cacheKey = "1";

@Null(message = "The expression is depcreated since 0.8. Please use cache-key instead")
@Deprecated
private String expression;

/**
* The number of tokens that should be consumed
*/
@NotNull
@Min(1)
private Integer numTokens = 1;

@NotEmpty
@Valid
private List<BandWidth> bandwidths = new ArrayList<>();

/**
* The token inheritance strategy to use when replacing the configuration of a bucket
*/
@NotNull
private TokensInheritanceStrategy tokensInheritanceStrategy = TokensInheritanceStrategy.RESET;

/**
* SpEl condition to check if the rate limit should be executed. If null there is no check.
*/
private String executeCondition;

/**
* TODO comment
*/
private String postExecuteCondition;

@Valid
private List<ExecutePredicateDefinition> executePredicates = new ArrayList<>();

/**
* SpEl condition to check if the rate-limit should apply. If null there is no check.
*/
private String skipCondition;

@Valid
private List<ExecutePredicateDefinition> skipPredicates = new ArrayList<>();

/**
* SPEL expression to dynamic evaluate filter key
*/
@NotBlank
private String cacheKey = "1";

/**
* The number of tokens that should be consumed
*/
@NotNull
@Min(1)
private Integer numTokens = 1;

@NotEmpty
@Valid
private List<BandWidth> bandwidths = new ArrayList<>();

/**
* The token inheritance strategy to use when replacing the configuration of a bucket
*/
@NotNull
private TokensInheritanceStrategy tokensInheritanceStrategy = TokensInheritanceStrategy.RESET;

public RateLimit copy() {
var copy = new RateLimit();
copy.setExecuteCondition(this.executeCondition);
copy.setPostExecuteCondition(this.postExecuteCondition);
copy.setExecutePredicates(this.executePredicates);
copy.setSkipCondition(this.skipCondition);
copy.setSkipPredicates(this.skipPredicates);
copy.setCacheKey(this.cacheKey);
copy.setNumTokens(this.numTokens);
copy.setBandwidths(this.bandwidths);
copy.setTokensInheritanceStrategy(this.tokensInheritanceStrategy);
return copy;
}

public void consumeNotNullValues(RateLimit toConsume) {
if(toConsume == null) {
return;
}

if (toConsume.getExecuteCondition() != null && !toConsume.getExecuteCondition().isEmpty()) {
this.setExecuteCondition(toConsume.getExecuteCondition());
}
if (toConsume.getPostExecuteCondition() != null && !toConsume.getPostExecuteCondition().isEmpty()) {
this.setPostExecuteCondition(toConsume.getPostExecuteCondition());
}
if (toConsume.getExecutePredicates() != null && !toConsume.getExecutePredicates().isEmpty()) {
this.setExecutePredicates(toConsume.getExecutePredicates());
}
if (toConsume.getSkipCondition() != null && !toConsume.getSkipCondition().isEmpty()) {
this.setSkipCondition(toConsume.getSkipCondition());
}
if (toConsume.getSkipPredicates() != null && !toConsume.getSkipPredicates().isEmpty()) {
this.setSkipPredicates(toConsume.getSkipPredicates());
}
if (toConsume.getCacheKey() != null && !toConsume.getCacheKey().equals("1") && !toConsume.getCacheKey().isEmpty()) {
this.setCacheKey(toConsume.getCacheKey());
}
if(toConsume.getNumTokens() != null && toConsume.getNumTokens() != 1) {
this.setNumTokens(toConsume.getNumTokens());
}
if(toConsume.getBandwidths() != null && !toConsume.getBandwidths().isEmpty()) {
this.setBandwidths(toConsume.getBandwidths());
}
if(toConsume.getTokensInheritanceStrategy() != null) {
this.setTokensInheritanceStrategy(toConsume.getTokensInheritanceStrategy());
}
}

}
10 changes: 10 additions & 0 deletions bucket4j-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@
<version>${redisson.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.giffing.bucket4j.spring.boot.starter.config.aspect;

import com.giffing.bucket4j.spring.boot.starter.config.cache.Bucket4jCacheConfiguration;
import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver;
import com.giffing.bucket4j.spring.boot.starter.config.metrics.actuator.SpringBootActuatorConfig;
import com.giffing.bucket4j.spring.boot.starter.config.service.ServiceConfiguration;
import com.giffing.bucket4j.spring.boot.starter.context.properties.Bucket4JBootProperties;
import com.giffing.bucket4j.spring.boot.starter.service.RateLimitService;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ConditionalOnClass(Aspect.class)
@ConditionalOnProperty(prefix = Bucket4JBootProperties.PROPERTY_PREFIX, value = {"enabled"}, matchIfMissing = true)
@EnableConfigurationProperties({Bucket4JBootProperties.class})
@AutoConfigureAfter(value = { CacheAutoConfiguration.class, Bucket4jCacheConfiguration.class })
@ConditionalOnBean(value = SyncCacheResolver.class)
@Import(value = {ServiceConfiguration.class, Bucket4jCacheConfiguration.class, SpringBootActuatorConfig.class})
public class AopConfig {

@Bean
public RateLimitAspect rateLimitAspect(RateLimitService rateLimitService, Bucket4JBootProperties bucket4JBootProperties, SyncCacheResolver syncCacheResolver) {
return new RateLimitAspect(rateLimitService, bucket4JBootProperties.getMethods(), syncCacheResolver);
}

}
Loading

0 comments on commit 206c6cf

Please sign in to comment.