From 8f0971efcdb6571d0524cdd84bccd970164c6309 Mon Sep 17 00:00:00 2001 From: lepdou Date: Thu, 14 Jul 2022 13:09:31 +0800 Subject: [PATCH 1/4] add feature-env plugin & add spring cloud gateway staining plugin (#428) --- CHANGELOG.md | 1 + pom.xml | 1 + .../CustomTransitiveMetadataResolver.java | 36 +- .../core/EncodeTransferMedataScgFilter.java | 3 +- .../CustomTransitiveMetadataResolverTest.java | 23 +- .../ratelimit/RateLimitRuleLabelResolver.java | 2 +- .../filter/QuotaCheckReactiveFilter.java | 4 +- .../filter/QuotaCheckServletFilter.java | 4 +- .../filter/QuotaCheckReactiveFilterTest.java | 8 +- .../filter/QuotaCheckServletFilterTest.java | 8 +- .../PolarisLoadBalancerCompositeRule.java | 110 +++--- .../polaris/router/PolarisRouterContext.java | 61 +++- .../router/RouterRuleLabelResolver.java | 2 +- .../router/config/RibbonConfiguration.java | 18 +- .../config/RouterAutoConfiguration.java | 22 ++ .../feign/FeignExpressionLabelUtils.java | 2 +- .../feign/PolarisFeignLoadBalancer.java | 4 +- .../MetadataRouterRequestInterceptor.java | 57 ++++ .../NearbyRouterRequestInterceptor.java | 53 +++ .../RuleBasedRouterRequestInterceptor.java | 59 ++++ .../PolarisLoadBalancerInterceptor.java | 8 +- .../scg/PolarisLoadBalancerClientFilter.java | 8 +- .../router/spi/RouterRequestInterceptor.java | 37 ++ .../router/spi/RouterResponseInterceptor.java | 38 +++ .../PolarisLoadBalancerCompositeRuleTest.java | 65 ++-- .../router/PolarisRouterContextTest.java | 67 +++- .../feign/PolarisFeignLoadBalancerTest.java | 2 +- .../PolarisLoadBalancerInterceptorTest.java | 10 +- .../PolarisLoadBalancerClientFilterTest.java | 2 +- .../common/constant/MetadataConstant.java | 21 ++ .../config/MetadataAutoConfiguration.java | 15 - .../gateway/MetadataFirstScgFilter.java | 62 ---- .../tencent/cloud/common/rule/Condition.java | 65 ++++ .../cloud/common/rule/ConditionUtils.java | 48 +++ .../com/tencent/cloud/common/rule/KVPair.java | 54 +++ .../cloud/common/rule/KVPairUtils.java | 49 +++ .../tencent/cloud/common/rule/Operation.java | 133 ++++++++ .../common/util/ExpressionLabelUtils.java | 319 ------------------ .../cloud/common/util/JacksonUtils.java | 10 + .../expresstion/ExpressionLabelUtils.java | 136 ++++++++ .../ServletExpressionLabelUtils.java | 96 ++++++ .../SpringWebExpressionLabelUtils.java | 161 +++++++++ .../config/MetadataAutoConfigurationTest.java | 10 - .../cloud/common/rule/OperationTest.java | 97 ++++++ .../common/util/ExpressionLabelUtilsTest.java | 9 +- spring-cloud-tencent-coverage/pom.xml | 10 + spring-cloud-tencent-dependencies/pom.xml | 15 +- .../gateway-callee-service/pom.xml | 7 +- .../src/main/resources/bootstrap.yml | 2 - .../gateway-callee-service2/pom.xml | 5 + .../gateway-scg-service/pom.xml | 10 + .../src/main/resources/bootstrap.yml | 14 +- spring-cloud-tencent-plugin-starters/pom.xml | 22 ++ .../pom.xml | 30 ++ .../FeatureEnvAutoConfiguration.java | 42 +++ .../featureenv/FeatureEnvProperties.java | 39 +++ .../FeatureEnvRouterRequestInterceptor.java | 65 ++++ ...itional-spring-configuration-metadata.json | 10 + .../main/resources/META-INF/spring.factories | 2 + ...eatureEnvRouterRequestInterceptorTest.java | 104 ++++++ .../pom.xml | 34 ++ .../gateway/SCGPluginsAutoConfiguration.java | 84 +++++ .../gateway/staining/StainingProperties.java | 39 +++ .../gateway/staining/TrafficStainer.java | 38 +++ .../TrafficStainingGatewayFilter.java | 104 ++++++ .../staining/rule/RuleStainingExecutor.java | 70 ++++ .../staining/rule/RuleStainingProperties.java | 73 ++++ .../staining/rule/RuleTrafficStainer.java | 57 ++++ .../gateway/staining/rule/StainingRule.java | 78 +++++ .../staining/rule/StainingRuleManager.java | 80 +++++ ...itional-spring-configuration-metadata.json | 40 +++ .../main/resources/META-INF/spring.factories | 2 + .../TrafficStainerGatewayFilterTest.java | 89 +++++ .../rule/RuleStainingExecutorTest.java | 181 ++++++++++ .../rule/StainingRuleManagerTest.java | 133 ++++++++ 75 files changed, 2897 insertions(+), 582 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java create mode 100644 spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java delete mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java delete mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java create mode 100644 spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java create mode 100644 spring-cloud-tencent-plugin-starters/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java create mode 100644 spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e02220c7a..b44658e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ - [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413) - [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417) +- [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428) diff --git a/pom.xml b/pom.xml index d13aac07e..0839b7199 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ spring-cloud-starter-tencent-polaris-ratelimit spring-cloud-starter-tencent-polaris-circuitbreaker spring-cloud-starter-tencent-polaris-router + spring-cloud-tencent-plugin-starters spring-cloud-tencent-dependencies spring-cloud-tencent-examples spring-cloud-tencent-coverage diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java index b4303f6f1..f37ff1405 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolver.java @@ -25,6 +25,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; +import com.tencent.cloud.common.constant.MetadataConstant; import org.apache.commons.lang.StringUtils; import org.springframework.http.HttpHeaders; @@ -38,9 +39,6 @@ import org.springframework.web.server.ServerWebExchange; */ public final class CustomTransitiveMetadataResolver { - private static final String TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-"; - private static final int TRANSITIVE_HEADER_PREFIX_LENGTH = TRANSITIVE_HEADER_PREFIX.length(); - private CustomTransitiveMetadataResolver() { } @@ -50,10 +48,20 @@ public final class CustomTransitiveMetadataResolver { HttpHeaders headers = exchange.getRequest().getHeaders(); for (Map.Entry> entry : headers.entrySet()) { String key = entry.getKey(); - if (StringUtils.isNotBlank(key) - && StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX) + if (StringUtils.isBlank(key)) { + continue; + } + // resolve sct transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX) + && !CollectionUtils.isEmpty(entry.getValue())) { + String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH); + result.put(sourceKey, entry.getValue().get(0)); + } + + //resolve polaris transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX) && !CollectionUtils.isEmpty(entry.getValue())) { - String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH); + String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH); result.put(sourceKey, entry.getValue().get(0)); } } @@ -67,11 +75,21 @@ public final class CustomTransitiveMetadataResolver { Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = headers.nextElement(); + if (StringUtils.isBlank(key)) { + continue; + } + + // resolve sct transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX) + && StringUtils.isNotBlank(request.getHeader(key))) { + String sourceKey = StringUtils.substring(key, MetadataConstant.SCT_TRANSITIVE_HEADER_PREFIX_LENGTH); + result.put(sourceKey, request.getHeader(key)); + } - if (StringUtils.isNotBlank(key) - && StringUtils.startsWithIgnoreCase(key, TRANSITIVE_HEADER_PREFIX) + // resolve polaris transitive header + if (StringUtils.startsWithIgnoreCase(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX) && StringUtils.isNotBlank(request.getHeader(key))) { - String sourceKey = StringUtils.substring(key, TRANSITIVE_HEADER_PREFIX_LENGTH); + String sourceKey = StringUtils.substring(key, MetadataConstant.POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH); result.put(sourceKey, request.getHeader(key)); } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java index e6c2946e8..f8274b506 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/EncodeTransferMedataScgFilter.java @@ -59,11 +59,10 @@ public class EncodeTransferMedataScgFilter implements GlobalFilter, Ordered { // get metadata of current thread MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); - - // add new metadata and cover old if (metadataContext == null) { metadataContext = MetadataContextHolder.get(); } + Map customMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); if (!CollectionUtils.isEmpty(customMetadata)) { String metadataStr = JacksonUtils.serialize2Json(customMetadata); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java index 9dc83bd51..a1b286456 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/CustomTransitiveMetadataResolverTest.java @@ -34,7 +34,7 @@ import org.springframework.mock.web.server.MockServerWebExchange; public class CustomTransitiveMetadataResolverTest { @Test - public void test() { + public void testSCTTransitiveMetadata() { MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get(""); builder.header("X-SCT-Metadata-Transitive-a", "test"); MockServerWebExchange exchange = MockServerWebExchange.from(builder); @@ -44,11 +44,30 @@ public class CustomTransitiveMetadataResolverTest { } @Test - public void testServlet() { + public void testPolarisTransitiveMetadata() { + MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get(""); + builder.header("X-Polaris-Metadata-Transitive-a", "test"); + MockServerWebExchange exchange = MockServerWebExchange.from(builder); + Map resolve = CustomTransitiveMetadataResolver.resolve(exchange); + Assertions.assertThat(resolve.size()).isEqualTo(1); + Assertions.assertThat(resolve.get("a")).isEqualTo("test"); + } + + @Test + public void testSCTServletTransitiveMetadata() { MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("X-SCT-Metadata-Transitive-a", "test"); Map resolve = CustomTransitiveMetadataResolver.resolve(request); Assertions.assertThat(resolve.size()).isEqualTo(1); Assertions.assertThat(resolve.get("a")).isEqualTo("test"); } + + @Test + public void testPolarisServletTransitiveMetadata() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("X-Polaris-Metadata-Transitive-a", "test"); + Map resolve = CustomTransitiveMetadataResolver.resolve(request); + Assertions.assertThat(resolve.size()).isEqualTo(1); + Assertions.assertThat(resolve.get("a")).isEqualTo("test"); + } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java index bee50dc5c..dc61665c0 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/RateLimitRuleLabelResolver.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.polaris.client.pb.ModelProto; import com.tencent.polaris.client.pb.RateLimitProto; diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java index cdca48224..cb95857ba 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilter.java @@ -28,7 +28,7 @@ import javax.annotation.PostConstruct; import com.google.common.collect.Maps; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -161,6 +161,6 @@ public class QuotaCheckReactiveFilter implements WebFilter, Ordered { private Map getRuleExpressionLabels(ServerWebExchange exchange, String namespace, String service) { Set expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); - return ExpressionLabelUtils.resolve(exchange, expressionLabels); + return SpringWebExpressionLabelUtils.resolve(exchange, expressionLabels); } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java index 8497bdf4e..82c3d7915 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/main/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilter.java @@ -31,7 +31,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -158,6 +158,6 @@ public class QuotaCheckServletFilter extends OncePerRequestFilter { private Map getRuleExpressionLabels(HttpServletRequest request, String namespace, String service) { Set expressionLabels = rateLimitRuleLabelResolver.getExpressionLabelKeys(namespace, service); - return ExpressionLabelUtils.resolve(request, expressionLabels); + return ServletExpressionLabelUtils.resolve(request, expressionLabels); } } diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java index b54ffec6f..9e2da2c18 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckReactiveFilterTest.java @@ -26,7 +26,7 @@ import java.util.concurrent.CountDownLatch; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.constant.RateLimitConstant; @@ -75,15 +75,15 @@ import static org.mockito.Mockito.when; public class QuotaCheckReactiveFilterTest { private static MockedStatic mockedApplicationContextAwareUtils; - private static MockedStatic expressionLabelUtilsMockedStatic; + private static MockedStatic expressionLabelUtilsMockedStatic; private final PolarisRateLimiterLabelReactiveResolver labelResolver = exchange -> Collections.singletonMap("ReactiveResolver", "ReactiveResolver"); private QuotaCheckReactiveFilter quotaCheckReactiveFilter; @BeforeClass public static void beforeClass() { - expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class); - when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) + expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class); + when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); diff --git a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java index cd68a8fdb..05d490d0c 100644 --- a/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-ratelimit/src/test/java/com/tencent/cloud/polaris/ratelimit/filter/QuotaCheckServletFilterTest.java @@ -30,7 +30,7 @@ import javax.servlet.http.HttpServletRequest; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.ratelimit.RateLimitRuleLabelResolver; import com.tencent.cloud.polaris.ratelimit.config.PolarisRateLimitProperties; import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelServletResolver; @@ -74,7 +74,7 @@ import static org.mockito.Mockito.when; public class QuotaCheckServletFilterTest { private static MockedStatic mockedApplicationContextAwareUtils; - private static MockedStatic expressionLabelUtilsMockedStatic; + private static MockedStatic expressionLabelUtilsMockedStatic; private PolarisRateLimiterLabelServletResolver labelResolver = exchange -> Collections.singletonMap("ServletResolver", "ServletResolver"); private QuotaCheckServletFilter quotaCheckServletFilter; @@ -82,8 +82,8 @@ public class QuotaCheckServletFilterTest { @BeforeClass public static void beforeClass() { - expressionLabelUtilsMockedStatic = mockStatic(ExpressionLabelUtils.class); - when(ExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) + expressionLabelUtilsMockedStatic = mockStatic(SpringWebExpressionLabelUtils.class); + when(SpringWebExpressionLabelUtils.resolve(any(ServerWebExchange.class), anySet())) .thenReturn(Collections.singletonMap("RuleLabelResolver", "RuleLabelResolver")); mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java index 4be83e682..f3763a13f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRule.java @@ -19,10 +19,7 @@ package com.tencent.cloud.polaris.router; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; @@ -40,15 +37,11 @@ import com.tencent.cloud.common.pojo.PolarisServer; import com.tencent.cloud.polaris.loadbalancer.LoadBalancerUtils; import com.tencent.cloud.polaris.loadbalancer.PolarisWeightedRule; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; -import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; -import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; -import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; import com.tencent.polaris.api.pojo.Instance; import com.tencent.polaris.api.pojo.ServiceInfo; import com.tencent.polaris.api.pojo.ServiceInstances; -import com.tencent.polaris.plugins.router.metadata.MetadataRouter; -import com.tencent.polaris.plugins.router.nearby.NearbyRouter; -import com.tencent.polaris.plugins.router.rule.RuleBasedRouter; import com.tencent.polaris.router.api.core.RouterAPI; import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; @@ -82,24 +75,21 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { final static String STRATEGY_AVAILABILITY_FILTERING = "availabilityFilteringRule"; private final PolarisLoadBalancerProperties loadBalancerProperties; - private final PolarisNearByRouterProperties polarisNearByRouterProperties; - private final PolarisMetadataRouterProperties polarisMetadataRouterProperties; - private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; private final RouterAPI routerAPI; + private final List requestInterceptors; + private final List responseInterceptors; private final AbstractLoadBalancerRule delegateRule; public PolarisLoadBalancerCompositeRule(RouterAPI routerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties, - IClientConfig iClientConfig) { + IClientConfig iClientConfig, + List requestInterceptors, + List responseInterceptors) { this.routerAPI = routerAPI; - this.polarisNearByRouterProperties = polarisNearByRouterProperties; this.loadBalancerProperties = polarisLoadBalancerProperties; - this.polarisMetadataRouterProperties = polarisMetadataRouterProperties; - this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties; + this.requestInterceptors = requestInterceptors; + this.responseInterceptors = responseInterceptors; delegateRule = getRule(); delegateRule.initWithNiwsConfig(iClientConfig); @@ -118,80 +108,66 @@ public class PolarisLoadBalancerCompositeRule extends AbstractLoadBalancerRule { } // 2. filter by router - List serversAfterRouter = doRouter(allServers, key); - - // 3. filter by load balance. - // A LoadBalancer needs to be regenerated for each request, - // because the list of servers may be different after filtered by router - ILoadBalancer loadBalancer = new SimpleLoadBalancer(); - loadBalancer.addServers(serversAfterRouter); - delegateRule.setLoadBalancer(loadBalancer); + if (key instanceof PolarisRouterContext) { + PolarisRouterContext routerContext = (PolarisRouterContext) key; + List serversAfterRouter = doRouter(allServers, routerContext); + // 3. filter by load balance. + // A LoadBalancer needs to be regenerated for each request, + // because the list of servers may be different after filtered by router + ILoadBalancer loadBalancer = new SimpleLoadBalancer(); + loadBalancer.addServers(serversAfterRouter); + delegateRule.setLoadBalancer(loadBalancer); + } return delegateRule.choose(key); } - List doRouter(List allServers, Object key) { + List doRouter(List allServers, PolarisRouterContext routerContext) { ServiceInstances serviceInstances = LoadBalancerUtils.transferServersToServiceInstances(allServers); - // filter instance by routers - ProcessRoutersRequest processRoutersRequest = buildProcessRoutersRequest(serviceInstances, key); + ProcessRoutersRequest processRoutersRequest = buildProcessRoutersBaseRequest(serviceInstances); + // process request interceptors + processRouterRequestInterceptors(processRoutersRequest, routerContext); + + // process router chain ProcessRoutersResponse processRoutersResponse = routerAPI.processRouters(processRoutersRequest); - List filteredInstances = new ArrayList<>(); + // process response interceptors + processRouterResponseInterceptors(routerContext, processRoutersResponse); + + // transfer polaris server to ribbon server ServiceInstances filteredServiceInstances = processRoutersResponse.getServiceInstances(); + List filteredInstances = new ArrayList<>(); for (Instance instance : filteredServiceInstances.getInstances()) { filteredInstances.add(new PolarisServer(serviceInstances, instance)); } + return filteredInstances; } - ProcessRoutersRequest buildProcessRoutersRequest(ServiceInstances serviceInstances, Object key) { + ProcessRoutersRequest buildProcessRoutersBaseRequest(ServiceInstances serviceInstances) { ProcessRoutersRequest processRoutersRequest = new ProcessRoutersRequest(); processRoutersRequest.setDstInstances(serviceInstances); - - // metadata router - if (polarisMetadataRouterProperties.isEnabled()) { - Map transitiveLabels = getRouterLabels(key, PolarisRouterContext.TRANSITIVE_LABELS); - processRoutersRequest.putRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, transitiveLabels); - } - - // nearby router - if (polarisNearByRouterProperties.isEnabled()) { - Map nearbyRouterMetadata = new HashMap<>(); - nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true"); - processRoutersRequest.putRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata); - } - - // rule based router - // set dynamic switch for rule based router - boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled(); - Map ruleRouterMetadata = new HashMap<>(); - ruleRouterMetadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled)); - processRoutersRequest.putRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, ruleRouterMetadata); - ServiceInfo serviceInfo = new ServiceInfo(); serviceInfo.setNamespace(MetadataContext.LOCAL_NAMESPACE); serviceInfo.setService(MetadataContext.LOCAL_SERVICE); - - if (ruleBasedRouterEnabled) { - Map ruleRouterLabels = getRouterLabels(key, PolarisRouterContext.RULE_ROUTER_LABELS); - // The label information that the rule based routing depends on - // is placed in the metadata of the source service for transmission. - // Later, can consider putting it in routerMetadata like other routers. - serviceInfo.setMetadata(ruleRouterLabels); - } - processRoutersRequest.setSourceService(serviceInfo); - return processRoutersRequest; } - private Map getRouterLabels(Object key, String type) { - if (key instanceof PolarisRouterContext) { - return ((PolarisRouterContext) key).getLabels(type); + void processRouterRequestInterceptors(ProcessRoutersRequest processRoutersRequest, PolarisRouterContext routerContext) { + for (RouterRequestInterceptor requestInterceptor : requestInterceptors) { + requestInterceptor.apply(processRoutersRequest, routerContext); + } + } + + private void processRouterResponseInterceptors(PolarisRouterContext routerContext, ProcessRoutersResponse processRoutersResponse) { + if (!CollectionUtils.isEmpty(responseInterceptors)) { + for (RouterResponseInterceptor responseInterceptor : responseInterceptors) { + responseInterceptor.apply(processRoutersResponse, routerContext); + } } - return Collections.emptyMap(); } public AbstractLoadBalancerRule getRule() { diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java index 1e05d2ec6..b2569bd19 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java @@ -18,9 +18,14 @@ package com.tencent.cloud.polaris.router; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -30,15 +35,14 @@ import org.springframework.util.CollectionUtils; * @author lepdou 2022-05-17 */ public class PolarisRouterContext { - /** - * the label for rule router. + * the labels for rule router, contain transitive metadata. */ - public static final String RULE_ROUTER_LABELS = "ruleRouter"; + public static final String ROUTER_LABELS = "allMetadata"; /** * transitive labels. */ - public static final String TRANSITIVE_LABELS = "transitive"; + public static final String TRANSITIVE_LABELS = "transitiveMetadata"; private Map> labels; @@ -53,7 +57,54 @@ public class PolarisRouterContext { return Collections.unmodifiableMap(subLabels); } - public void setLabels(String labelType, Map subLabels) { + public Map getLabels(String labelType, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map typeLabels = getLabels(labelType); + if (CollectionUtils.isEmpty(typeLabels)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + for (String key : labelKeys) { + String value = typeLabels.get(key); + if (StringUtils.isNotBlank(value)) { + labels.put(key, value); + } + } + return labels; + } + + public String getLabel(String labelKey) { + Map routerLabels = labels.get(ROUTER_LABELS); + if (CollectionUtils.isEmpty(routerLabels)) { + return StringUtils.EMPTY; + } + return routerLabels.get(labelKey); + } + + public Set getLabelAsSet(String labelKey) { + Map routerLabels = labels.get(ROUTER_LABELS); + if (CollectionUtils.isEmpty(routerLabels)) { + return Collections.emptySet(); + } + + for (Map.Entry entry : routerLabels.entrySet()) { + if (StringUtils.equalsIgnoreCase(labelKey, entry.getKey())) { + String keysStr = entry.getValue(); + if (StringUtils.isNotBlank(keysStr)) { + String[] keysArr = StringUtils.split(keysStr, ","); + return new HashSet<>(Arrays.asList(keysArr)); + } + } + } + + return Collections.emptySet(); + } + + public void putLabels(String labelType, Map subLabels) { if (this.labels == null) { this.labels = new HashMap<>(); } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java index ded1784f4..f0df59de1 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/RouterRuleLabelResolver.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import com.tencent.cloud.polaris.context.ServiceRuleManager; import com.tencent.polaris.client.pb.ModelProto; import com.tencent.polaris.client.pb.RoutingProto; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java index e8fe2f06b..79cc513ee 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RibbonConfiguration.java @@ -18,13 +18,14 @@ package com.tencent.cloud.polaris.router.config; +import java.util.List; + import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.IRule; import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperties; import com.tencent.cloud.polaris.router.PolarisLoadBalancerCompositeRule; -import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; -import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; -import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterResponseInterceptor; import com.tencent.polaris.router.api.core.RouterAPI; import org.springframework.context.annotation.Bean; @@ -39,12 +40,9 @@ public class RibbonConfiguration { @Bean public IRule polarisLoadBalancerCompositeRule(RouterAPI routerAPI, PolarisLoadBalancerProperties polarisLoadBalancerProperties, - PolarisNearByRouterProperties polarisNearByRouterProperties, - PolarisMetadataRouterProperties polarisMetadataRouterProperties, - PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties, - IClientConfig iClientConfig) { - return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, - polarisNearByRouterProperties, polarisMetadataRouterProperties, - polarisRuleBasedRouterProperties, iClientConfig); + IClientConfig iClientConfig, List requestInterceptors, + List responseInterceptors) { + return new PolarisLoadBalancerCompositeRule(routerAPI, polarisLoadBalancerProperties, iClientConfig, + requestInterceptors, responseInterceptors); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java index 6364c0502..0ac1b7021 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/config/RouterAutoConfiguration.java @@ -25,8 +25,12 @@ import com.tencent.cloud.polaris.router.beanprocessor.LoadBalancerInterceptorBea import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -63,4 +67,22 @@ public class RouterAutoConfiguration { public RouterRuleLabelResolver routerRuleLabelResolver(ServiceRuleManager serviceRuleManager) { return new RouterRuleLabelResolver(serviceRuleManager); } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.polaris.router.metadata-router.enabled", matchIfMissing = true) + public MetadataRouterRequestInterceptor metadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) { + return new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties); + } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.polaris.router.nearby-router.enabled", matchIfMissing = true) + public NearbyRouterRequestInterceptor nearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) { + return new NearbyRouterRequestInterceptor(polarisNearByRouterProperties); + } + + @Bean + @ConditionalOnProperty(value = "spring.cloud.polaris.router.rule-router.enabled", matchIfMissing = true) + public RuleBasedRouterRequestInterceptor ruleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + return new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties); + } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java index c3d19b35e..b040a6f7c 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/FeignExpressionLabelUtils.java @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import feign.RequestTemplate; import org.apache.commons.lang.StringUtils; diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java index b57bf30c1..618d6c2f5 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancer.java @@ -71,7 +71,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)); Map labelHeaderValuesMap = new HashMap<>(); @@ -86,7 +86,7 @@ public class PolarisFeignLoadBalancer extends FeignLoadBalancer { catch (UnsupportedEncodingException e) { throw new RuntimeException("unsupported charset exception " + UTF_8); } - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labelHeaderValuesMap); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labelHeaderValuesMap); return routerContext; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java new file mode 100644 index 000000000..9b9a78f2b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.interceptor; + +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * Router request interceptor for metadata router. + * @author lepdou 2022-07-06 + */ +public class MetadataRouterRequestInterceptor implements RouterRequestInterceptor { + private static final String LABEL_KEY_METADATA_ROUTER_KEYS = "system-metadata-router-keys"; + + private final PolarisMetadataRouterProperties polarisMetadataRouterProperties; + + public MetadataRouterRequestInterceptor(PolarisMetadataRouterProperties polarisMetadataRouterProperties) { + this.polarisMetadataRouterProperties = polarisMetadataRouterProperties; + } + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + if (!polarisMetadataRouterProperties.isEnabled()) { + return; + } + + // 1. get metadata router label keys + Set metadataRouterKeys = routerContext.getLabelAsSet(LABEL_KEY_METADATA_ROUTER_KEYS); + // 2. get metadata router labels + Map metadataRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS, + metadataRouterKeys); + // 3. set metadata router labels to request + request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, metadataRouterLabels); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java new file mode 100644 index 000000000..86efcd62e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/NearbyRouterRequestInterceptor.java @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.interceptor; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.plugins.router.nearby.NearbyRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * Router request interceptor for nearby router. + * @author lepdou 2022-07-06 + */ +public class NearbyRouterRequestInterceptor implements RouterRequestInterceptor { + + private final PolarisNearByRouterProperties polarisNearByRouterProperties; + + public NearbyRouterRequestInterceptor(PolarisNearByRouterProperties polarisNearByRouterProperties) { + this.polarisNearByRouterProperties = polarisNearByRouterProperties; + } + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + if (!polarisNearByRouterProperties.isEnabled()) { + return; + } + + Map nearbyRouterMetadata = new HashMap<>(); + nearbyRouterMetadata.put(NearbyRouter.ROUTER_ENABLED, "true"); + + request.addRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY, nearbyRouterMetadata); + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java new file mode 100644 index 000000000..9abba566f --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java @@ -0,0 +1,59 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.interceptor; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.plugins.router.rule.RuleBasedRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * Router request interceptor for rule based router. + * @author lepdou 2022-07-06 + */ +public class RuleBasedRouterRequestInterceptor implements RouterRequestInterceptor { + + private final PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; + + public RuleBasedRouterRequestInterceptor(PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties) { + this.polarisRuleBasedRouterProperties = polarisRuleBasedRouterProperties; + } + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + boolean ruleBasedRouterEnabled = polarisRuleBasedRouterProperties.isEnabled(); + + // set dynamic switch for rule based router + Map metadata = new HashMap<>(); + metadata.put(RuleBasedRouter.ROUTER_ENABLED, String.valueOf(ruleBasedRouterEnabled)); + request.addRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED, metadata); + + // The label information that the rule based routing depends on + // is placed in the metadata of the source service for transmission. + // Later, can consider putting it in routerMetadata like other routers. + if (ruleBasedRouterEnabled) { + Map ruleRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + request.getSourceService().setMetadata(ruleRouterLabels); + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java index ddb215149..c427075c9 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptor.java @@ -30,7 +30,7 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; @@ -137,8 +137,8 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); return routerContext; } @@ -151,6 +151,6 @@ public class PolarisLoadBalancerInterceptor extends LoadBalancerInterceptor { return Collections.emptyMap(); } - return ExpressionLabelUtils.resolve(request, labelKeys); + return SpringWebExpressionLabelUtils.resolve(request, labelKeys); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.java index c46bc6e55..c610a0c9c 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilter.java @@ -29,7 +29,7 @@ import java.util.Set; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; -import com.tencent.cloud.common.util.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.RouterLabelResolver; @@ -126,8 +126,8 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); return routerContext; } @@ -140,6 +140,6 @@ public class PolarisLoadBalancerClientFilter extends LoadBalancerClientFilter { return Collections.emptyMap(); } - return ExpressionLabelUtils.resolve(exchange, labelKeys); + return SpringWebExpressionLabelUtils.resolve(exchange, labelKeys); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java new file mode 100644 index 000000000..6e515d9c2 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterRequestInterceptor.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.spi; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; + +/** + * The interceptor for router request. Router plugin can modify request by interceptor. + * + * @author lepdou 2022-07-11 + */ +public interface RouterRequestInterceptor { + + /** + * processing request. + * @param request the router request. + * @param routerContext the router context. + */ + void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext); +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java new file mode 100644 index 000000000..5d80d0c25 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/spi/RouterResponseInterceptor.java @@ -0,0 +1,38 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.router.spi; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.polaris.router.api.rpc.ProcessRoutersResponse; + +/** + * The interceptor for router response. Router plugin can modify router response by interceptor. + * + * @author lepdou 2022-07-11 + */ +public interface RouterResponseInterceptor { + + /** + * processing router response. + * + * @param response the router response. + * @param routerContext the router context. + */ + void apply(ProcessRoutersResponse response, PolarisRouterContext routerContext); +} diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java index 4caf5d08e..19cde939e 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisLoadBalancerCompositeRuleTest.java @@ -18,6 +18,7 @@ package com.tencent.cloud.polaris.router; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -44,6 +45,10 @@ import com.tencent.cloud.polaris.loadbalancer.config.PolarisLoadBalancerProperti import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; import com.tencent.cloud.polaris.router.config.properties.PolarisNearByRouterProperties; import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; +import com.tencent.cloud.polaris.router.interceptor.MetadataRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.NearbyRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.interceptor.RuleBasedRouterRequestInterceptor; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; import com.tencent.polaris.api.pojo.DefaultInstance; import com.tencent.polaris.api.pojo.DefaultServiceInstances; import com.tencent.polaris.api.pojo.Instance; @@ -87,23 +92,28 @@ public class PolarisLoadBalancerCompositeRuleTest { private PolarisRuleBasedRouterProperties polarisRuleBasedRouterProperties; @Mock private RouterAPI routerAPI; + private IClientConfig config; private String testNamespace = "testNamespace"; private String testCallerService = "testCallerService"; private String testCalleeService = "testCalleeService"; + private final List requestInterceptors = new ArrayList<>(); + @Before public void before() { config = new DefaultClientConfigImpl(); config.loadDefaultValues(); + requestInterceptors.add(new MetadataRouterRequestInterceptor(polarisMetadataRouterProperties)); + requestInterceptors.add(new NearbyRouterRequestInterceptor(polarisNearByRouterProperties)); + requestInterceptors.add(new RuleBasedRouterRequestInterceptor(polarisRuleBasedRouterProperties)); } @Test public void testGetDefaultLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(""); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule defaultRule = compositeRule.getRule(); @@ -114,8 +124,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testRandomLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RANDOM); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -126,8 +135,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testWeightLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_WEIGHT); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -138,8 +146,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testRetryLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RETRY); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -150,8 +157,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testWeightedResponseTimeLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_RESPONSE_TIME_WEIGHTED); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -162,8 +168,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void tesBestAvailableLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_BEST_AVAILABLE); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -174,8 +179,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void tesRoundRobinLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_ROUND_ROBIN); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -186,8 +190,7 @@ public class PolarisLoadBalancerCompositeRuleTest { public void testAvailabilityFilteringLB() { when(polarisLoadBalancerProperties.getStrategy()).thenReturn(PolarisLoadBalancerCompositeRule.STRATEGY_AVAILABILITY_FILTERING); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); AbstractLoadBalancerRule lbRule = compositeRule.getRule(); @@ -206,13 +209,18 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + Map oldRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + Map newRouterLabels = new HashMap<>(oldRouterLabels); + newRouterLabels.put("system-metadata-router-keys", "k2"); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, newRouterLabels); + + ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances); + compositeRule.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA); @@ -236,13 +244,13 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances); + compositeRule.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(NearbyRouter.ROUTER_TYPE_NEAR_BY); @@ -267,13 +275,13 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - ProcessRoutersRequest request = compositeRule.buildProcessRoutersRequest(serviceInstances, routerContext); + ProcessRoutersRequest request = compositeRule.buildProcessRoutersBaseRequest(serviceInstances); + compositeRule.processRouterRequestInterceptors(request, routerContext); Map routerMetadata = request.getRouterMetadata(RuleBasedRouter.ROUTER_TYPE_RULE_BASED); @@ -298,8 +306,7 @@ public class PolarisLoadBalancerCompositeRuleTest { setTransitiveMetadata(); PolarisLoadBalancerCompositeRule compositeRule = new PolarisLoadBalancerCompositeRule(routerAPI, - polarisLoadBalancerProperties, polarisNearByRouterProperties, - polarisMetadataRouterProperties, polarisRuleBasedRouterProperties, config); + polarisLoadBalancerProperties, config, requestInterceptors, null); ProcessRoutersResponse assembleResponse = assembleProcessRoutersResponse(); when(routerAPI.processRouters(any())).thenReturn(assembleResponse); @@ -339,8 +346,8 @@ public class PolarisLoadBalancerCompositeRuleTest { Map routerLabels = new HashMap<>(); routerLabels.put("k2", "v2"); routerLabels.put("k3", "v3"); - routerContext.setLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, routerLabels); + routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, routerLabels); return routerContext; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java index 0f2094233..ced54a597 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java @@ -20,7 +20,9 @@ package com.tencent.cloud.polaris.router; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import com.google.common.collect.Sets; import org.junit.Assert; import org.junit.Test; @@ -38,27 +40,76 @@ public class PolarisRouterContextTest { labels.put("k2", "v2"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, labels); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); - Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1")); - Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2")); - Assert.assertNull(routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k3")); + Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); + Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1")); + Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2")); + Assert.assertNull(routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k3")); } @Test public void testSetNull() { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.setLabels(PolarisRouterContext.RULE_ROUTER_LABELS, null); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, null); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); } @Test public void testGetEmptyRouterContext() { PolarisRouterContext routerContext = new PolarisRouterContext(); Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); + } + + @Test + public void testGetLabelByKeys() { + Map labels = new HashMap<>(); + labels.put("k1", "v1"); + labels.put("k2", "v2"); + labels.put("k3", "v3"); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + Map resolvedLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS, + Sets.newHashSet("k1", "k2", "k4")); + + Assert.assertEquals(2, resolvedLabels.size()); + Assert.assertEquals("v1", resolvedLabels.get("k1")); + Assert.assertEquals("v2", resolvedLabels.get("k2")); + } + + @Test + public void testGetLabel() { + Map labels = new HashMap<>(); + labels.put("k1", "v1"); + labels.put("k2", "v2"); + labels.put("k3", "v3"); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + String resolvedLabel = routerContext.getLabel("k1"); + + Assert.assertEquals("v1", resolvedLabel); + } + + @Test + public void testGetLabelAsSet() { + Map labels = new HashMap<>(); + labels.put("k1", "v1,v2,v3"); + + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + Set resolvedLabels = routerContext.getLabelAsSet("k1"); + + Assert.assertEquals(3, resolvedLabels.size()); + Assert.assertTrue(resolvedLabels.contains("v1")); + Assert.assertTrue(resolvedLabels.contains("v2")); + Assert.assertTrue(resolvedLabels.contains("v3")); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java index 5feda8a90..930abe89e 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/PolarisFeignLoadBalancerTest.java @@ -85,7 +85,7 @@ public class PolarisFeignLoadBalancerTest { PolarisRouterContext routerContext = polarisFeignLoadBalancer.buildRouterContext(headers); Assert.assertNotNull(routerContext); - Map routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS); + Map routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); Assert.assertNotNull(routerLabels); Assert.assertEquals("v1", routerLabels.get("k1")); Assert.assertEquals("v2", routerLabels.get("k2")); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java index 0514c5b6b..66bd4fe5f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/PolarisLoadBalancerInterceptorTest.java @@ -210,11 +210,11 @@ public class PolarisLoadBalancerInterceptorTest { Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k1")); Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).get("k2")); - Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k1")); - Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k2")); - Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("k4")); - Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.method}")); - Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS).get("${http.uri}")); + Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1")); + Assert.assertEquals("v22", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2")); + Assert.assertEquals("v4", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k4")); + Assert.assertEquals("GET", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.method}")); + Assert.assertEquals("/user/get", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("${http.uri}")); } } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java index b4c22b04f..469f9d700 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisLoadBalancerClientFilterTest.java @@ -128,7 +128,7 @@ public class PolarisLoadBalancerClientFilterTest { PolarisRouterContext routerContext = polarisLoadBalancerClientFilter.genRouterContext(webExchange, calleeService); - Map routerLabels = routerContext.getLabels(PolarisRouterContext.RULE_ROUTER_LABELS); + Map routerLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); Assert.assertEquals("v1", routerLabels.get("${http.header.k1}")); Assert.assertEquals("zhangsan", routerLabels.get("${http.query.userid}")); Assert.assertEquals("blue", routerLabels.get("env")); diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java index 09e17e1e8..89f2c79dd 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/MetadataConstant.java @@ -26,6 +26,27 @@ import org.springframework.core.Ordered; */ public final class MetadataConstant { + /** + * sct transitive header prefix. + */ + public static final String SCT_TRANSITIVE_HEADER_PREFIX = "X-SCT-Metadata-Transitive-"; + /** + * sct transitive header prefix length. + */ + public static final int SCT_TRANSITIVE_HEADER_PREFIX_LENGTH = SCT_TRANSITIVE_HEADER_PREFIX.length(); + + /** + * polaris transitive header prefix. + */ + public static final String POLARIS_TRANSITIVE_HEADER_PREFIX = "X-Polaris-Metadata-Transitive-"; + /** + * polaris transitive header prefix length. + */ + public static final int POLARIS_TRANSITIVE_HEADER_PREFIX_LENGTH = POLARIS_TRANSITIVE_HEADER_PREFIX.length(); + + private MetadataConstant() { + + } /** * Order of filter, interceptor, ... */ diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java index a8a5d59a4..060bed30d 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfiguration.java @@ -19,11 +19,8 @@ package com.tencent.cloud.common.metadata.config; import com.tencent.cloud.common.metadata.StaticMetadataManager; -import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter; import com.tencent.cloud.common.spi.InstanceMetadataProvider; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.lang.Nullable; @@ -51,16 +48,4 @@ public class MetadataAutoConfiguration { return new StaticMetadataManager(metadataLocalProperties, instanceMetadataProvider); } - /** - * Create when gateway application is SCG. - */ - @Configuration - @ConditionalOnClass(name = "org.springframework.cloud.gateway.filter.GlobalFilter") - static class MetadataScgFilterConfig { - - @Bean - public GlobalFilter metadataFirstScgFilter() { - return new MetadataFirstScgFilter(); - } - } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java deleted file mode 100644 index e73c2bf70..000000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/filter/gateway/MetadataFirstScgFilter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - */ - -package com.tencent.cloud.common.metadata.filter.gateway; - -import com.tencent.cloud.common.constant.MetadataConstant; -import com.tencent.cloud.common.metadata.MetadataContext; -import com.tencent.cloud.common.metadata.MetadataContextHolder; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER; - -/** - * Scg output first filter used for setting peer info in context. - * - * @author Haotian Zhang - */ -public class MetadataFirstScgFilter implements GlobalFilter, Ordered { - - /** - * Order of MetadataFirstScgFilter. - */ - public static final int METADATA_FIRST_FILTER_ORDER = ROUTE_TO_URL_FILTER_ORDER + 1; - - @Override - public int getOrder() { - return METADATA_FIRST_FILTER_ORDER; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // get metadata of current thread - MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); - if (metadataContext == null) { - metadataContext = MetadataContextHolder.get(); - } - - exchange.getAttributes().put(MetadataConstant.HeaderName.METADATA_CONTEXT, metadataContext); - - return chain.filter(exchange); - } -} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java new file mode 100644 index 000000000..6823594ed --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Condition.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.rule; + +import java.util.List; + +/** + * Condition expression. + * @author lepdou 2022-07-06 + */ +public class Condition { + + private String key; + private String operation; + private List values; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + @Override + public String toString() { + return "Condition{" + + "key='" + key + '\'' + + ", values='" + values + '\'' + + ", operation='" + operation + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java new file mode 100644 index 000000000..6092c0516 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/ConditionUtils.java @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.rule; + +import java.util.List; +import java.util.Map; + +/** + * The util for condition expression. + * @author lepdou 2022-07-11 + */ +public final class ConditionUtils { + + private ConditionUtils() { + } + + public static boolean match(Map actualValues, List conditions) { + boolean allMatched = true; + for (Condition condition : conditions) { + List expectedValues = condition.getValues(); + String operation = condition.getOperation(); + String key = condition.getKey(); + String actualValue = actualValues.get(key); + + if (!Operation.match(expectedValues, actualValue, operation)) { + allMatched = false; + break; + } + } + return allMatched; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java new file mode 100644 index 000000000..cd722fa15 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPair.java @@ -0,0 +1,54 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.rule; + +/** + * Key/value pair. + * @author lepdou 2022-07-06 + */ +public class KVPair { + + private String key; + private String value; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + + @Override + public String toString() { + return "KVPair{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java new file mode 100644 index 000000000..11edcefd8 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/KVPairUtils.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.rule; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.util.CollectionUtils; + +/** + * The util for key/value pair. + * @author lepdou 2022-07-11 + */ +public final class KVPairUtils { + + private KVPairUtils() { + } + + public static Map toMap(List labels) { + if (CollectionUtils.isEmpty(labels)) { + return Collections.emptyMap(); + } + + Map result = new HashMap<>(); + labels.forEach(label -> { + result.put(label.getKey(), label.getValue()); + }); + + return result; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java new file mode 100644 index 000000000..1d32638f3 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/rule/Operation.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.rule; + +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * The condition operation. + * @author lepdou 2022-07-11 + */ +public enum Operation { + + /** + * case sensitive string equals. + */ + EQUAL("EQUAL"), + /** + * case sensitive string not equals. + */ + NOT_EQUAL("NOT_EQUAL"), + /** + * whether element in collection. + */ + IN("IN"), + /** + * whether element not in collection. + */ + NOT_IN("NOT_IN"), + /** + * regex operation. + */ + REGEX("REGEX"), + /** + * whether element is blank. + */ + BLANK("BLANK"), + /** + * whether element is not blank. + */ + NOT_BLANK("NOT_BLANK"); + + private final String value; + + Operation(String value) { + this.value = value; + } + + public static boolean match(List expectedValues, String actualValue, String rawOperation) { + String firstExpectedValue = null; + if (!CollectionUtils.isEmpty(expectedValues)) { + firstExpectedValue = expectedValues.get(0); + } + + switch (getOperation(rawOperation)) { + case EQUAL: + return firstExpectedValue != null && StringUtils.equals(actualValue, firstExpectedValue); + case NOT_EQUAL: + return firstExpectedValue == null || !StringUtils.equals(actualValue, firstExpectedValue); + case BLANK: + return StringUtils.isBlank(actualValue); + case NOT_BLANK: + return !StringUtils.isBlank(actualValue); + case IN: + if (CollectionUtils.isEmpty(expectedValues)) { + return false; + } + return expectedValues.contains(actualValue); + case NOT_IN: + if (CollectionUtils.isEmpty(expectedValues)) { + return true; + } + return !expectedValues.contains(actualValue); + case REGEX: + if (firstExpectedValue == null) { + return false; + } + Pattern r = Pattern.compile(firstExpectedValue); + return r.matcher(actualValue).matches(); + default: + return false; + } + } + + public static Operation getOperation(String operation) { + if (StringUtils.equalsIgnoreCase(operation, EQUAL.value)) { + return EQUAL; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_EQUAL.value)) { + return NOT_EQUAL; + } + if (StringUtils.equalsIgnoreCase(operation, IN.value)) { + return IN; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_IN.value)) { + return NOT_IN; + } + if (StringUtils.equalsIgnoreCase(operation, REGEX.value)) { + return REGEX; + } + if (StringUtils.equalsIgnoreCase(operation, BLANK.value)) { + return BLANK; + } + if (StringUtils.equalsIgnoreCase(operation, NOT_BLANK.value)) { + return NOT_BLANK; + } + throw new RuntimeException("Unsupported operation. operation = " + operation); + } + + public String getValue() { + return value; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java deleted file mode 100644 index dde5bc254..000000000 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/ExpressionLabelUtils.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. - * - * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. - * - * Licensed under the BSD 3-Clause License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://opensource.org/licenses/BSD-3-Clause - * - * Unless required by applicable law or agreed to in writing, software distributed - * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - * CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - */ - -package com.tencent.cloud.common.util; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.lang.StringUtils; - -import org.springframework.http.HttpCookie; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.web.server.ServerWebExchange; - -/** - * the utils for parse label expression. - * - * @author lepdou 2022-05-13 - * @author cheese8 2022-06-20 - */ -public final class ExpressionLabelUtils { - - /** - * the expression prefix of header label. - */ - public static final String LABEL_HEADER_PREFIX = "${http.header."; - /** - * the length of expression header label prefix. - */ - public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length(); - /** - * the expression prefix of query. - */ - public static final String LABEL_QUERY_PREFIX = "${http.query."; - /** - * the length of expression query label prefix. - */ - public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length(); - /** - * the expression prefix of cookie. - */ - public static final String LABEL_COOKIE_PREFIX = "${http.cookie."; - /** - * the length of expression cookie label prefix. - */ - public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length(); - /** - * the expression of method. - */ - public static final String LABEL_METHOD = "${http.method}"; - /** - * the expression of uri. - */ - public static final String LABEL_URI = "${http.uri}"; - /** - * the suffix of expression. - */ - public static final String LABEL_SUFFIX = "}"; - private ExpressionLabelUtils() { - } - - public static boolean isExpressionLabel(String labelKey) { - if (StringUtils.isEmpty(labelKey)) { - return false; - } - if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) || - StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) { - return true; - } - return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) || - StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) || - StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) - && StringUtils.endsWith(labelKey, LABEL_SUFFIX); - } - - public static Map resolve(HttpServletRequest request, Set labelKeys) { - if (CollectionUtils.isEmpty(labelKeys)) { - return Collections.emptyMap(); - } - - Map labels = new HashMap<>(); - - for (String labelKey : labelKeys) { - if (!isExpressionLabel(labelKey)) { - continue; - } - if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = parseHeaderKey(labelKey); - if (StringUtils.isBlank(headerKey)) { - continue; - } - labels.put(labelKey, request.getHeader(headerKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = parseQueryKey(labelKey); - if (StringUtils.isBlank(queryKey)) { - continue; - } - labels.put(labelKey, getQueryValue(request.getQueryString(), queryKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) { - String cookieKey = parseCookieKey(labelKey); - if (StringUtils.isBlank(cookieKey)) { - continue; - } - labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey)); - } - else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { - labels.put(labelKey, request.getMethod()); - } - else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { - labels.put(labelKey, request.getRequestURI()); - } - } - - return labels; - } - - public static Map resolve(ServerWebExchange exchange, Set labelKeys) { - if (CollectionUtils.isEmpty(labelKeys)) { - return Collections.emptyMap(); - } - - Map labels = new HashMap<>(); - - for (String labelKey : labelKeys) { - if (!isExpressionLabel(labelKey)) { - continue; - } - if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = parseHeaderKey(labelKey); - if (StringUtils.isBlank(headerKey)) { - continue; - } - labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = parseQueryKey(labelKey); - if (StringUtils.isBlank(queryKey)) { - continue; - } - labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) { - String cookieKey = parseCookieKey(labelKey); - if (StringUtils.isBlank(cookieKey)) { - continue; - } - labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey)); - } - else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { - labels.put(labelKey, exchange.getRequest().getMethodValue()); - } - else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { - labels.put(labelKey, exchange.getRequest().getURI().getPath()); - } - } - - return labels; - } - - public static Map resolve(HttpRequest request, Set labelKeys) { - if (CollectionUtils.isEmpty(labelKeys)) { - return Collections.emptyMap(); - } - - Map labels = new HashMap<>(); - - for (String labelKey : labelKeys) { - if (!isExpressionLabel(labelKey)) { - continue; - } - if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX)) { - String headerKey = parseHeaderKey(labelKey); - if (StringUtils.isBlank(headerKey)) { - continue; - } - labels.put(labelKey, getHeaderValue(request, headerKey)); - } - else if (StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX)) { - String queryKey = parseQueryKey(labelKey); - if (StringUtils.isBlank(queryKey)) { - continue; - } - labels.put(labelKey, getQueryValue(request, queryKey)); - } - else if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey)) { - labels.put(labelKey, request.getMethodValue()); - } - else if (StringUtils.equalsIgnoreCase(LABEL_URI, labelKey)) { - labels.put(labelKey, request.getURI().getPath()); - } - } - - return labels; - } - - public static String parseHeaderKey(String expression) { - return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1); - } - - public static String parseQueryKey(String expression) { - return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1); - } - - public static String parseCookieKey(String expression) { - return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1); - } - - public static String getQueryValue(String queryString, String queryKey) { - if (StringUtils.isBlank(queryString)) { - return StringUtils.EMPTY; - } - String[] queries = StringUtils.split(queryString, "&"); - if (queries == null || queries.length == 0) { - return StringUtils.EMPTY; - } - for (String query : queries) { - String[] queryKV = StringUtils.split(query, "="); - if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) { - return queryKV[1]; - } - } - return StringUtils.EMPTY; - } - - public static String getCookieValue(Cookie[] cookies, String key) { - if (cookies == null || cookies.length == 0) { - return StringUtils.EMPTY; - } - for (Cookie cookie : cookies) { - if (StringUtils.equals(cookie.getName(), key)) { - return cookie.getValue(); - } - } - return StringUtils.EMPTY; - } - - public static String getHeaderValue(ServerHttpRequest request, String key) { - String value = request.getHeaders().getFirst(key); - if (value == null) { - return StringUtils.EMPTY; - } - return value; - } - - public static String getQueryValue(ServerHttpRequest request, String key) { - MultiValueMap queries = request.getQueryParams(); - if (CollectionUtils.isEmpty(queries)) { - return StringUtils.EMPTY; - } - String value = queries.getFirst(key); - if (value == null) { - return StringUtils.EMPTY; - } - return value; - } - - public static String getCookieValue(ServerHttpRequest request, String key) { - HttpCookie cookie = request.getCookies().getFirst(key); - if (cookie == null) { - return StringUtils.EMPTY; - } - return cookie.getValue(); - } - - public static String getHeaderValue(HttpRequest request, String key) { - HttpHeaders headers = request.getHeaders(); - return headers.getFirst(key); - } - - public static String getQueryValue(HttpRequest request, String key) { - String query = request.getURI().getQuery(); - return getQueryValue(query, key); - } - - public static String getFirstValue(Map> valueMaps, String key) { - if (CollectionUtils.isEmpty(valueMaps)) { - return StringUtils.EMPTY; - } - - Collection values = valueMaps.get(key); - - if (CollectionUtils.isEmpty(values)) { - return StringUtils.EMPTY; - } - - for (String value : values) { - return value; - } - - return StringUtils.EMPTY; - } -} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java index 4ed4d2ea1..c693fef26 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java @@ -61,6 +61,16 @@ public final class JacksonUtils { } } + public static T deserialize(String jsonStr, Class type) { + try { + return OM.readValue(jsonStr, type); + } + catch (JsonProcessingException e) { + LOG.error("Json to object failed. {}", type, e); + throw new RuntimeException("Json to object failed.", e); + } + } + /** * Json to Map. * @param jsonStr Json String diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java new file mode 100644 index 000000000..f42c5b106 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ExpressionLabelUtils.java @@ -0,0 +1,136 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.util.expresstion; + +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * the utils for parse label expression. + * + * @author lepdou 2022-05-13 + * @author cheese8 2022-06-20 + */ +public final class ExpressionLabelUtils { + + /** + * the expression prefix of header label. + */ + public static final String LABEL_HEADER_PREFIX = "${http.header."; + /** + * the length of expression header label prefix. + */ + public static final int LABEL_HEADER_PREFIX_LEN = LABEL_HEADER_PREFIX.length(); + /** + * the expression prefix of query. + */ + public static final String LABEL_QUERY_PREFIX = "${http.query."; + /** + * the length of expression query label prefix. + */ + public static final int LABEL_QUERY_PREFIX_LEN = LABEL_QUERY_PREFIX.length(); + /** + * the expression prefix of cookie. + */ + public static final String LABEL_COOKIE_PREFIX = "${http.cookie."; + /** + * the length of expression cookie label prefix. + */ + public static final int LABEL_COOKIE_PREFIX_LEN = LABEL_COOKIE_PREFIX.length(); + /** + * the expression of method. + */ + public static final String LABEL_METHOD = "${http.method}"; + /** + * the expression of uri. + */ + public static final String LABEL_URI = "${http.uri}"; + /** + * the suffix of expression. + */ + public static final String LABEL_SUFFIX = "}"; + + private ExpressionLabelUtils() { + } + + public static boolean isExpressionLabel(String labelKey) { + if (StringUtils.isEmpty(labelKey)) { + return false; + } + if (StringUtils.equalsIgnoreCase(LABEL_METHOD, labelKey) || + StringUtils.startsWithIgnoreCase(LABEL_URI, labelKey)) { + return true; + } + return (StringUtils.startsWithIgnoreCase(labelKey, LABEL_HEADER_PREFIX) || + StringUtils.startsWithIgnoreCase(labelKey, LABEL_QUERY_PREFIX) || + StringUtils.startsWithIgnoreCase(labelKey, LABEL_COOKIE_PREFIX)) + && StringUtils.endsWith(labelKey, LABEL_SUFFIX); + } + + public static String parseHeaderKey(String expression) { + return expression.substring(LABEL_HEADER_PREFIX_LEN, expression.length() - 1); + } + + public static String parseQueryKey(String expression) { + return expression.substring(LABEL_QUERY_PREFIX_LEN, expression.length() - 1); + } + + public static String parseCookieKey(String expression) { + return expression.substring(LABEL_COOKIE_PREFIX_LEN, expression.length() - 1); + } + + public static String getQueryValue(String queryString, String queryKey) { + if (StringUtils.isBlank(queryString)) { + return StringUtils.EMPTY; + } + String[] queries = StringUtils.split(queryString, "&"); + if (queries == null || queries.length == 0) { + return StringUtils.EMPTY; + } + for (String query : queries) { + String[] queryKV = StringUtils.split(query, "="); + if (queryKV != null && queryKV.length == 2 && StringUtils.equals(queryKV[0], queryKey)) { + return queryKV[1]; + } + } + return StringUtils.EMPTY; + } + + public static String getFirstValue(Map> valueMaps, String key) { + if (CollectionUtils.isEmpty(valueMaps)) { + return StringUtils.EMPTY; + } + + Collection values = valueMaps.get(key); + + if (CollectionUtils.isEmpty(values)) { + return StringUtils.EMPTY; + } + + for (String value : values) { + return value; + } + + return StringUtils.EMPTY; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java new file mode 100644 index 000000000..cf504fffb --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/ServletExpressionLabelUtils.java @@ -0,0 +1,96 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.util.expresstion; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.util.CollectionUtils; + +/** + * Parse labels from HttpServletRequest. + * @author lepdou 2022-07-11 + */ +public final class ServletExpressionLabelUtils { + + private ServletExpressionLabelUtils() { + } + + public static Map resolve(HttpServletRequest request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) { + continue; + } + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, request.getHeader(headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, ExpressionLabelUtils.getQueryValue(request.getQueryString(), queryKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_COOKIE_PREFIX)) { + String cookieKey = ExpressionLabelUtils.parseCookieKey(labelKey); + if (StringUtils.isBlank(cookieKey)) { + continue; + } + labels.put(labelKey, getCookieValue(request.getCookies(), cookieKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.getMethod()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, request.getRequestURI()); + } + } + + return labels; + } + + public static String getCookieValue(Cookie[] cookies, String key) { + if (cookies == null || cookies.length == 0) { + return StringUtils.EMPTY; + } + for (Cookie cookie : cookies) { + if (StringUtils.equals(cookie.getName(), key)) { + return cookie.getValue(); + } + } + return StringUtils.EMPTY; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java new file mode 100644 index 000000000..ebf27607b --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/expresstion/SpringWebExpressionLabelUtils.java @@ -0,0 +1,161 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.util.expresstion; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; + +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; + +/** + * Parse labels from ServerWebExchange and HttpRequest. + * @author lepdou 2022-07-11 + */ +public final class SpringWebExpressionLabelUtils { + + private SpringWebExpressionLabelUtils() { + } + + public static Map resolve(ServerWebExchange exchange, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) { + continue; + } + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, getHeaderValue(exchange.getRequest(), headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, getQueryValue(exchange.getRequest(), queryKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_COOKIE_PREFIX)) { + String cookieKey = ExpressionLabelUtils.parseCookieKey(labelKey); + if (StringUtils.isBlank(cookieKey)) { + continue; + } + labels.put(labelKey, getCookieValue(exchange.getRequest(), cookieKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, exchange.getRequest().getMethodValue()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, exchange.getRequest().getURI().getPath()); + } + } + + return labels; + } + + public static Map resolve(HttpRequest request, Set labelKeys) { + if (CollectionUtils.isEmpty(labelKeys)) { + return Collections.emptyMap(); + } + + Map labels = new HashMap<>(); + + for (String labelKey : labelKeys) { + if (!ExpressionLabelUtils.isExpressionLabel(labelKey)) { + continue; + } + if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_HEADER_PREFIX)) { + String headerKey = ExpressionLabelUtils.parseHeaderKey(labelKey); + if (StringUtils.isBlank(headerKey)) { + continue; + } + labels.put(labelKey, getHeaderValue(request, headerKey)); + } + else if (StringUtils.startsWithIgnoreCase(labelKey, ExpressionLabelUtils.LABEL_QUERY_PREFIX)) { + String queryKey = ExpressionLabelUtils.parseQueryKey(labelKey); + if (StringUtils.isBlank(queryKey)) { + continue; + } + labels.put(labelKey, getQueryValue(request, queryKey)); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_METHOD, labelKey)) { + labels.put(labelKey, request.getMethodValue()); + } + else if (StringUtils.equalsIgnoreCase(ExpressionLabelUtils.LABEL_URI, labelKey)) { + labels.put(labelKey, request.getURI().getPath()); + } + } + + return labels; + } + + public static String getHeaderValue(ServerHttpRequest request, String key) { + String value = request.getHeaders().getFirst(key); + if (value == null) { + return StringUtils.EMPTY; + } + return value; + } + + public static String getQueryValue(ServerHttpRequest request, String key) { + MultiValueMap queries = request.getQueryParams(); + if (CollectionUtils.isEmpty(queries)) { + return StringUtils.EMPTY; + } + String value = queries.getFirst(key); + if (value == null) { + return StringUtils.EMPTY; + } + return value; + } + + public static String getCookieValue(ServerHttpRequest request, String key) { + HttpCookie cookie = request.getCookies().getFirst(key); + if (cookie == null) { + return StringUtils.EMPTY; + } + return cookie.getValue(); + } + + public static String getHeaderValue(HttpRequest request, String key) { + HttpHeaders headers = request.getHeaders(); + return headers.getFirst(key); + } + + public static String getQueryValue(HttpRequest request, String key) { + String query = request.getURI().getQuery(); + return ExpressionLabelUtils.getQueryValue(query, key); + } +} diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java index e73ee3a2b..75ba3d998 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataAutoConfigurationTest.java @@ -18,7 +18,6 @@ package com.tencent.cloud.common.metadata.config; -import com.tencent.cloud.common.metadata.filter.gateway.MetadataFirstScgFilter; import org.assertj.core.api.Assertions; import org.junit.Test; @@ -50,9 +49,6 @@ public class MetadataAutoConfigurationTest { .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); - Assertions.assertThat(context).hasSingleBean( - MetadataAutoConfiguration.MetadataScgFilterConfig.class); - Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class); }); } @@ -65,9 +61,6 @@ public class MetadataAutoConfigurationTest { .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); - Assertions.assertThat(context).hasSingleBean( - MetadataAutoConfiguration.MetadataScgFilterConfig.class); - Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class); }); } @@ -80,9 +73,6 @@ public class MetadataAutoConfigurationTest { .withConfiguration(AutoConfigurations.of(MetadataAutoConfiguration.class)) .run(context -> { Assertions.assertThat(context).hasSingleBean(MetadataLocalProperties.class); - Assertions.assertThat(context).hasSingleBean( - MetadataAutoConfiguration.MetadataScgFilterConfig.class); - Assertions.assertThat(context).hasSingleBean(MetadataFirstScgFilter.class); }); } } diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java new file mode 100644 index 000000000..c5ab0cd63 --- /dev/null +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/rule/OperationTest.java @@ -0,0 +1,97 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.common.rule; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Test for {@link Operation}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class OperationTest { + + @Test + public void testEqual() { + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v1", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v2", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList(""), "v2", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.EQUAL.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), "v1", Operation.EQUAL.getValue())); + } + + @Test + public void testNotEqual() { + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "v1", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "v2", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList(""), "v2", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_EQUAL.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_EQUAL.getValue())); + } + + @Test + public void testIn() { + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.IN.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.IN.getValue())); + } + + @Test + public void testNotIn() { + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v1", Operation.NOT_IN.getValue())); + Assert.assertFalse(Operation.match(Arrays.asList("v1", "v2", "v3"), "v2", Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "v4", Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), "", Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Arrays.asList("v1", "v2", "v3"), null, Operation.NOT_IN.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.NOT_IN.getValue())); + } + + @Test + public void testEmpty() { + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), null, Operation.BLANK.getValue())); + Assert.assertTrue(Operation.match(Collections.singletonList("v1"), "", Operation.BLANK.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), null, Operation.BLANK.getValue())); + } + + @Test + public void testNotEmpty() { + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), null, Operation.NOT_BLANK.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v1"), "", Operation.NOT_BLANK.getValue())); + Assert.assertFalse(Operation.match(Collections.emptyList(), null, Operation.NOT_BLANK.getValue())); + Assert.assertTrue(Operation.match(Collections.emptyList(), "v1", Operation.NOT_BLANK.getValue())); + } + + @Test + public void testRegex() { + Assert.assertTrue(Operation.match(Collections.singletonList("v[1~10]"), "v1", Operation.REGEX.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]"), "v12", Operation.REGEX.getValue())); + Assert.assertFalse(Operation.match(Collections.singletonList("v[1~10]*"), "v12", Operation.REGEX.getValue())); + } +} diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java index 76666482b..2e6f13ecc 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/ExpressionLabelUtilsTest.java @@ -23,6 +23,9 @@ import java.util.Map; import java.util.Set; import com.google.common.collect.Sets; +import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -105,7 +108,7 @@ public class ExpressionLabelUtilsTest { request.setMethod(HttpMethod.GET.name()); request.setRequestURI("/users"); - Map result = ExpressionLabelUtils.resolve(request, labelKeys); + Map result = ServletExpressionLabelUtils.resolve(request, labelKeys); Assert.assertEquals("zhangsan", result.get(validLabel1)); Assert.assertEquals("zhangsan", result.get(validLabel2)); @@ -149,7 +152,7 @@ public class ExpressionLabelUtilsTest { .cookie(new HttpCookie("uid", "zhangsan")).build(); MockServerWebExchange exchange = new MockServerWebExchange.Builder(httpRequest).build(); - Map result = ExpressionLabelUtils.resolve(exchange, labelKeys); + Map result = SpringWebExpressionLabelUtils.resolve(exchange, labelKeys); Assert.assertEquals("zhangsan", result.get(validLabel1)); Assert.assertEquals("zhangsan", result.get(validLabel2)); @@ -193,7 +196,7 @@ public class ExpressionLabelUtilsTest { request.setURI(URI.create("http://calleeService/user/get?uid=zhangsan")); request.getHeaders().add("uid", "zhangsan"); - Map result = ExpressionLabelUtils.resolve(request, labelKeys); + Map result = SpringWebExpressionLabelUtils.resolve(request, labelKeys); Assert.assertEquals("zhangsan", result.get(validLabel1)); Assert.assertEquals("zhangsan", result.get(validLabel2)); diff --git a/spring-cloud-tencent-coverage/pom.xml b/spring-cloud-tencent-coverage/pom.xml index 37903c143..077ee0c36 100644 --- a/spring-cloud-tencent-coverage/pom.xml +++ b/spring-cloud-tencent-coverage/pom.xml @@ -63,6 +63,16 @@ com.tencent.cloud spring-cloud-starter-tencent-polaris-config + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index 2027ffae6..02bbd20fc 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -71,7 +71,7 @@ 1.7.0-Hoxton.SR12-SNAPSHOT - 1.7.0 + 1.7.1-SNAPSHOT 1.2.11 4.5.1 1.12.10 @@ -151,6 +151,19 @@ ${revision} + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + ${revision} + + + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + ${revision} + + com.google.guava diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml index 4cb4412c6..cc37a455b 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/pom.xml @@ -19,9 +19,14 @@ spring-cloud-starter-tencent-polaris-discovery + + com.tencent.cloud + spring-cloud-starter-tencent-metadata-transfer + + org.springframework.boot spring-boot-starter-web - \ No newline at end of file + diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml index c6d40440f..137da64e1 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service/src/main/resources/bootstrap.yml @@ -7,8 +7,6 @@ spring: cloud: tencent: metadata: - content: - env: blue polaris: address: grpc://183.47.111.80:8091 namespace: default diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml index 6454f43d7..f96b25e15 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-callee-service2/pom.xml @@ -18,6 +18,11 @@ spring-cloud-starter-tencent-polaris-discovery + + com.tencent.cloud + spring-cloud-starter-tencent-metadata-transfer + + org.springframework.boot spring-boot-starter-web diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml index 755b04d63..24efc40c0 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/pom.xml @@ -29,6 +29,16 @@ spring-cloud-starter-tencent-polaris-router + + com.tencent.cloud + spring-cloud-tencent-gateway-plugin + + + + com.tencent.cloud + spring-cloud-tencent-featureenv-plugin + + org.springframework.cloud spring-cloud-starter-gateway diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml index f50100264..7cfe79ded 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-scg-service/src/main/resources/bootstrap.yml @@ -6,11 +6,15 @@ spring: name: GatewayScgService cloud: tencent: - metadata: - content: - env: blue - transitive: - - env + plugin: + scg: + staining: + enabled: true + rule-staining: + enabled: true + router: + feature-env: + enabled: true polaris: address: grpc://183.47.111.80:8091 namespace: default diff --git a/spring-cloud-tencent-plugin-starters/pom.xml b/spring-cloud-tencent-plugin-starters/pom.xml new file mode 100644 index 000000000..127315097 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/pom.xml @@ -0,0 +1,22 @@ + + + + spring-cloud-tencent + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-plugin-starters + pom + Spring Cloud Starter Tencent Solution + + + spring-cloud-tencent-featureenv-plugin + spring-cloud-tencent-gateway-plugin + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml new file mode 100644 index 000000000..25cce93dd --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/pom.xml @@ -0,0 +1,30 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-featureenv-plugin + Spring Cloud Tencent Feature Environment Plugin + + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-router + + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java new file mode 100644 index 000000000..d341ba345 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvAutoConfiguration.java @@ -0,0 +1,42 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.featureenv; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration for feature env. + * @author lepdou 2022-07-06 + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.router.feature-env.enabled", matchIfMissing = true) +public class FeatureEnvAutoConfiguration { + + @Bean + public FeatureEnvProperties featureEnvProperties() { + return new FeatureEnvProperties(); + } + + @Bean + public FeatureEnvRouterRequestInterceptor featureEnvRouterRequestInterceptor() { + return new FeatureEnvRouterRequestInterceptor(); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java new file mode 100644 index 000000000..4aa0aa451 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvProperties.java @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.featureenv; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for feature env. + * @author lepdou 2022-07-12 + */ +@ConfigurationProperties("spring.cloud.tencent.plugin.router.feature-env") +public class FeatureEnvProperties { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java new file mode 100644 index 000000000..eceb569eb --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptor.java @@ -0,0 +1,65 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.featureenv; + +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; +import com.tencent.polaris.api.rpc.MetadataFailoverType; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; +import org.apache.commons.lang.StringUtils; + +/** + * Build metadata router context for feature env scene. + * @author lepdou 2022-07-06 + */ +public class FeatureEnvRouterRequestInterceptor implements RouterRequestInterceptor { + + private static final String LABEL_KEY_FEATURE_ENV_ROUTER_KEY = "system-feature-env-router-label"; + private static final String DEFAULT_FEATURE_ENV_ROUTER_LABEL = "env"; + private static final String NOT_EXISTED_ENV = "NOT_EXISTED_ENV"; + + @Override + public void apply(ProcessRoutersRequest request, PolarisRouterContext routerContext) { + //1. get feature env router label key + String envLabelKey = routerContext.getLabel(LABEL_KEY_FEATURE_ENV_ROUTER_KEY); + if (StringUtils.isBlank(envLabelKey)) { + envLabelKey = DEFAULT_FEATURE_ENV_ROUTER_LABEL; + } + + //2. get feature env router label value + String envLabelValue = routerContext.getLabel(envLabelKey); + if (envLabelValue == null) { + // router to base env when not matched feature env + envLabelValue = NOT_EXISTED_ENV; + } + + //3. set env metadata to router request + Map envMetadata = new HashMap<>(); + envMetadata.put(envLabelKey, envLabelValue); + + request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, envMetadata); + + //4. set failover type to others + request.setMetadataFailoverType(MetadataFailoverType.METADATAFAILOVERNOTKEY); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..e67ca364e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,10 @@ +{ + "properties": [ + { + "name": "spring.cloud.tencent.plugin.router.feature-env.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for feature env plugin." + } + ] +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..b61fbdfab --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.plugin.featureenv.FeatureEnvAutoConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java new file mode 100644 index 000000000..b83f587cd --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java @@ -0,0 +1,104 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.featureenv; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.tencent.cloud.polaris.router.PolarisRouterContext; +import com.tencent.polaris.api.pojo.DefaultServiceInstances; +import com.tencent.polaris.api.pojo.ServiceInstances; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.plugins.router.metadata.MetadataRouter; +import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Test for {@link FeatureEnvRouterRequestInterceptor}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class FeatureEnvRouterRequestInterceptorTest { + + @Test + public void testDefaultRouterKey() { + Map labels = new HashMap<>(); + labels.put("env", "blue"); + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); + request.setDstInstances(serviceInstances); + + FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor(); + + interceptor.apply(request, routerContext); + + Map metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA); + Assert.assertEquals(1, metadataRouterLabels.size()); + Assert.assertEquals("blue", metadataRouterLabels.get("env")); + } + + @Test + public void testSpecifyRouterKey() { + Map labels = new HashMap<>(); + labels.put("system-feature-env-router-label", "specify-env"); + labels.put("specify-env", "blue"); + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); + request.setDstInstances(serviceInstances); + + FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor(); + + interceptor.apply(request, routerContext); + + Map metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA); + Assert.assertEquals(1, metadataRouterLabels.size()); + Assert.assertEquals("blue", metadataRouterLabels.get("specify-env")); + } + + @Test + public void testNotExistedEnvLabel() { + Map labels = new HashMap<>(); + labels.put("system-feature-env-router-label", "specify-env"); + PolarisRouterContext routerContext = new PolarisRouterContext(); + routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + + ProcessRoutersRequest request = new ProcessRoutersRequest(); + ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); + request.setDstInstances(serviceInstances); + + FeatureEnvRouterRequestInterceptor interceptor = new FeatureEnvRouterRequestInterceptor(); + + interceptor.apply(request, routerContext); + + Map metadataRouterLabels = request.getRouterMetadata().get(MetadataRouter.ROUTER_TYPE_METADATA); + Assert.assertEquals(1, metadataRouterLabels.size()); + Assert.assertEquals("NOT_EXISTED_ENV", metadataRouterLabels.get("specify-env")); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml new file mode 100644 index 000000000..06a9c65e0 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/pom.xml @@ -0,0 +1,34 @@ + + + + spring-cloud-tencent-plugin-starters + com.tencent.cloud + ${revision} + ../pom.xml + + 4.0.0 + + spring-cloud-tencent-gateway-plugin + Spring Cloud Tencent Gateway Plugin + + + + org.springframework.cloud + spring-cloud-gateway-server + provided + + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-config + + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java new file mode 100644 index 000000000..f0daf20a8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/SCGPluginsAutoConfiguration.java @@ -0,0 +1,84 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway; + +import java.util.List; + +import com.tencent.cloud.plugin.gateway.staining.StainingProperties; +import com.tencent.cloud.plugin.gateway.staining.TrafficStainer; +import com.tencent.cloud.plugin.gateway.staining.TrafficStainingGatewayFilter; +import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingExecutor; +import com.tencent.cloud.plugin.gateway.staining.rule.RuleStainingProperties; +import com.tencent.cloud.plugin.gateway.staining.rule.RuleTrafficStainer; +import com.tencent.cloud.plugin.gateway.staining.rule.StainingRuleManager; +import com.tencent.polaris.configuration.api.core.ConfigFileService; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Auto configuration for spring cloud gateway plugins. + * @author lepdou 2022-07-06 + */ +@Configuration +@ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.enabled", matchIfMissing = true) +public class SCGPluginsAutoConfiguration { + + @Configuration + @ConditionalOnProperty("spring.cloud.tencent.plugin.scg.staining.enabled") + public static class StainingPluginConfiguration { + + @Bean + public StainingProperties stainingProperties() { + return new StainingProperties(); + } + + @Configuration + @ConditionalOnProperty(value = "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled", matchIfMissing = true) + public static class RuleStainingPluginConfiguration { + + @Bean + public RuleStainingProperties ruleStainingProperties() { + return new RuleStainingProperties(); + } + + @Bean + public StainingRuleManager stainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) { + return new StainingRuleManager(stainingProperties, configFileService); + } + + @Bean + public TrafficStainingGatewayFilter trafficStainingGatewayFilter(List trafficStainer) { + return new TrafficStainingGatewayFilter(trafficStainer); + } + + @Bean + public RuleStainingExecutor ruleStainingExecutor() { + return new RuleStainingExecutor(); + } + + @Bean + public RuleTrafficStainer ruleTrafficStainer(StainingRuleManager stainingRuleManager, + RuleStainingExecutor ruleStainingExecutor) { + return new RuleTrafficStainer(stainingRuleManager, ruleStainingExecutor); + } + } + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java new file mode 100644 index 000000000..226563bb9 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/StainingProperties.java @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for traffic staining. + * @author lepdou 2022-07-07 + */ +@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining") +public class StainingProperties { + + private boolean enabled; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java new file mode 100644 index 000000000..0eb40c3a3 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainer.java @@ -0,0 +1,38 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining; + +import java.util.Map; + +import org.springframework.core.Ordered; +import org.springframework.web.server.ServerWebExchange; + +/** + * Staining according to request parameters. for example, when the request parameter uid=0, staining env=blue. + * @author lepdou 2022-07-06 + */ +public interface TrafficStainer extends Ordered { + + /** + * get stained labels from request. + * @param exchange the request. + * @return stained labels. + */ + Map apply(ServerWebExchange exchange); +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java new file mode 100644 index 000000000..dda97563a --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainingGatewayFilter.java @@ -0,0 +1,104 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.tencent.cloud.common.constant.MetadataConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER; + +/** + * Staining the request, and the stained labels will be passed to the link through transitive metadata. + * @author lepdou 2022-07-06 + */ +public class TrafficStainingGatewayFilter implements GlobalFilter, Ordered { + + private final List trafficStainers; + + public TrafficStainingGatewayFilter(List trafficStainers) { + if (!CollectionUtils.isEmpty(trafficStainers)) { + trafficStainers.sort(Comparator.comparingInt(Ordered::getOrder)); + } + this.trafficStainers = trafficStainers; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + if (CollectionUtils.isEmpty(trafficStainers)) { + return chain.filter(exchange); + } + + // 1. get stained labels from request + Map stainedLabels = getStainedLabels(exchange); + + if (CollectionUtils.isEmpty(stainedLabels)) { + return chain.filter(exchange); + } + + // 2. put stained labels to metadata context + ServerHttpRequest request = exchange.getRequest().mutate().headers((httpHeaders) -> { + MetadataContext metadataContext = exchange.getAttribute(MetadataConstant.HeaderName.METADATA_CONTEXT); + if (metadataContext == null) { + metadataContext = MetadataContextHolder.get(); + } + + Map oldTransitiveMetadata = metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE); + + // append new transitive metadata + Map newTransitiveMetadata = new HashMap<>(oldTransitiveMetadata); + newTransitiveMetadata.putAll(stainedLabels); + + metadataContext.putFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE, newTransitiveMetadata); + }).build(); + + return chain.filter(exchange.mutate().request(request).build()); + } + + Map getStainedLabels(ServerWebExchange exchange) { + Map stainedLabels = new HashMap<>(); + int size = trafficStainers.size(); + for (int i = size - 1; i >= 0; i--) { + TrafficStainer stainer = trafficStainers.get(i); + Map labels = stainer.apply(exchange); + if (!CollectionUtils.isEmpty(labels)) { + stainedLabels.putAll(labels); + } + } + return stainedLabels; + } + + @Override + public int getOrder() { + return ROUTE_TO_URL_FILTER_ORDER + 1; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java new file mode 100644 index 000000000..e58df27e7 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutor.java @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.tencent.cloud.common.rule.Condition; +import com.tencent.cloud.common.rule.ConditionUtils; +import com.tencent.cloud.common.rule.KVPairUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; + +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +/** + * Resolve labels from request by staining rule. + * @author lepdou 2022-07-11 + */ +public class RuleStainingExecutor { + + Map execute(ServerWebExchange exchange, StainingRule stainingRule) { + if (stainingRule == null) { + return Collections.emptyMap(); + } + + List rules = stainingRule.getRules(); + if (CollectionUtils.isEmpty(rules)) { + return Collections.emptyMap(); + } + + Map parsedLabels = new HashMap<>(); + + for (StainingRule.Rule rule : rules) { + List conditions = rule.getConditions(); + + Set keys = new HashSet<>(); + conditions.forEach(condition -> keys.add(condition.getKey())); + Map actualValues = SpringWebExpressionLabelUtils.resolve(exchange, keys); + + if (!ConditionUtils.match(actualValues, conditions)) { + continue; + } + + parsedLabels.putAll(KVPairUtils.toMap(rule.getLabels())); + } + + return parsedLabels; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java new file mode 100644 index 000000000..115e2ca4e --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingProperties.java @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The properties for rule staining. + * @author lepdou 2022-07-11 + */ +@ConfigurationProperties("spring.cloud.tencent.plugin.scg.staining.rule-staining") +public class RuleStainingProperties { + + @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace:${spring.cloud.tencent.namespace:default}}") + private String namespace; + + @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.group:${spring.application.name:spring-cloud-gateway}}") + private String group; + + @Value("${spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName:rule/staining.json}") + private String fileName; + + private boolean enabled = true; + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java new file mode 100644 index 000000000..f96ec2a70 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleTrafficStainer.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import java.util.Collections; +import java.util.Map; + +import com.tencent.cloud.plugin.gateway.staining.TrafficStainer; + +import org.springframework.web.server.ServerWebExchange; + +/** + * Staining the request by staining rules. + * @author lepdou 2022-07-06 + */ +public class RuleTrafficStainer implements TrafficStainer { + + private final StainingRuleManager stainingRuleManager; + private final RuleStainingExecutor ruleStainingExecutor; + + public RuleTrafficStainer(StainingRuleManager stainingRuleManager, RuleStainingExecutor ruleStainingExecutor) { + this.stainingRuleManager = stainingRuleManager; + this.ruleStainingExecutor = ruleStainingExecutor; + } + + @Override + public Map apply(ServerWebExchange exchange) { + StainingRule stainingRule = stainingRuleManager.getStainingRule(); + + if (stainingRule == null) { + return Collections.emptyMap(); + } + + return ruleStainingExecutor.execute(exchange, stainingRule); + } + + @Override + public int getOrder() { + return 0; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java new file mode 100644 index 000000000..d76145659 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRule.java @@ -0,0 +1,78 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import java.util.List; + +import com.tencent.cloud.common.rule.Condition; +import com.tencent.cloud.common.rule.KVPair; + +/** + * The rules for staining. + * @author lepdou 2022-07-07 + */ +public class StainingRule { + + private List rules; + + public List getRules() { + return rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + @Override + public String toString() { + return "StainingRule{" + + "rules=" + rules + + '}'; + } + + public static class Rule { + private List conditions; + private List labels; + + public List getConditions() { + return conditions; + } + + public void setConditions(List conditions) { + this.conditions = conditions; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + @Override + public String toString() { + return "Rule{" + + "conditions=" + conditions + + ", labels=" + labels + + '}'; + } + } + +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java new file mode 100644 index 000000000..2ecb0cce6 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManager.java @@ -0,0 +1,80 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.polaris.configuration.api.core.ConfigFile; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Fetch staining rule from polaris, and deserialize to {@link StainingRule}. + * @author lepdou 2022-07-07 + */ +public class StainingRuleManager { + private static final Logger LOGGER = LoggerFactory.getLogger(StainingRuleManager.class); + + private final RuleStainingProperties stainingProperties; + private final ConfigFileService configFileService; + + private StainingRule stainingRule; + + public StainingRuleManager(RuleStainingProperties stainingProperties, ConfigFileService configFileService) { + this.stainingProperties = stainingProperties; + this.configFileService = configFileService; + + initStainingRule(); + } + + private void initStainingRule() { + ConfigFile rulesFile = configFileService.getConfigFile(stainingProperties.getNamespace(), stainingProperties.getGroup(), + stainingProperties.getFileName()); + + rulesFile.addChangeListener(event -> { + LOGGER.info("[SCT] update scg staining rules. {}", event); + deserialize(event.getNewValue()); + }); + + String ruleJson = rulesFile.getContent(); + LOGGER.info("[SCT] init scg staining rules. {}", ruleJson); + + deserialize(ruleJson); + } + + private void deserialize(String ruleJsonStr) { + if (StringUtils.isBlank(ruleJsonStr)) { + stainingRule = null; + return; + } + + try { + stainingRule = JacksonUtils.deserialize(ruleJsonStr, StainingRule.class); + } + catch (Exception e) { + LOGGER.error("[SCT] deserialize staining rule error.", e); + throw e; + } + } + + public StainingRule getStainingRule() { + return stainingRule; + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..927618f86 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,40 @@ +{ + "properties": [ + { + "name": "spring.cloud.tencent.plugin.scg.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for spring cloud gateway plugin." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for spring cloud gateway staining plugin." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.enabled", + "type": "java.lang.Boolean", + "defaultValue": true, + "description": "the switch for spring cloud gateway rule staining plugin." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.namespace", + "type": "java.lang.String", + "defaultValue": "default", + "description": "The namespace used to config staining rules." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.group", + "type": "java.lang.String", + "defaultValue": "${spring.application.name}", + "description": "The group used to config staining rules." + }, + { + "name": "spring.cloud.tencent.plugin.scg.staining.rule-staining.fileName", + "type": "java.lang.String", + "defaultValue": "rule/staining.json", + "description": "The file name used to config staining rules." + } + ] +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..70c95961b --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.tencent.cloud.plugin.gateway.SCGPluginsAutoConfiguration diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java new file mode 100644 index 000000000..6339691db --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/TrafficStainerGatewayFilterTest.java @@ -0,0 +1,89 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.util.CollectionUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test for {@link TrafficStainingGatewayFilter}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class TrafficStainerGatewayFilterTest { + + @Mock + private GatewayFilterChain chain; + @Mock + private ServerWebExchange exchange; + + @Test + public void testNoneTrafficStainingImplement() { + TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(null); + + when(chain.filter(exchange)).thenReturn(Mono.empty()); + + filter.filter(exchange, chain); + + verify(chain).filter(exchange); + } + + @Test + public void testMultiStaining() { + TrafficStainer trafficStainer1 = Mockito.mock(TrafficStainer.class); + TrafficStainer trafficStainer2 = Mockito.mock(TrafficStainer.class); + + when(trafficStainer1.getOrder()).thenReturn(1); + when(trafficStainer2.getOrder()).thenReturn(2); + + Map labels1 = new HashMap<>(); + labels1.put("k1", "v1"); + labels1.put("k2", "v2"); + when(trafficStainer1.apply(exchange)).thenReturn(labels1); + + Map labels2 = new HashMap<>(); + labels2.put("k1", "v11"); + labels2.put("k3", "v3"); + when(trafficStainer2.apply(exchange)).thenReturn(labels2); + + TrafficStainingGatewayFilter filter = new TrafficStainingGatewayFilter(Arrays.asList(trafficStainer1, trafficStainer2)); + Map result = filter.getStainedLabels(exchange); + + Assert.assertFalse(CollectionUtils.isEmpty(result)); + Assert.assertEquals("v1", result.get("k1")); + Assert.assertEquals("v2", result.get("k2")); + Assert.assertEquals("v3", result.get("k3")); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java new file mode 100644 index 000000000..5bb4757d3 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/RuleStainingExecutorTest.java @@ -0,0 +1,181 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import com.tencent.cloud.common.rule.Condition; +import com.tencent.cloud.common.rule.KVPair; +import com.tencent.cloud.common.rule.Operation; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; + +/** + * Test for {@link RuleStainingExecutor}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class RuleStainingExecutorTest { + + @Test + public void testMatchCondition() { + Condition condition1 = new Condition(); + condition1.setKey("${http.header.uid}"); + condition1.setOperation(Operation.EQUAL.toString()); + condition1.setValues(Collections.singletonList("1000")); + + Condition condition2 = new Condition(); + condition2.setKey("${http.query.source}"); + condition2.setOperation(Operation.IN.toString()); + condition2.setValues(Collections.singletonList("wx")); + + StainingRule.Rule rule = new StainingRule.Rule(); + rule.setConditions(Arrays.asList(condition1, condition2)); + + KVPair kvPair = new KVPair(); + kvPair.setKey("env"); + kvPair.setValue("blue"); + rule.setLabels(Collections.singletonList(kvPair)); + + StainingRule stainingRule = new StainingRule(); + stainingRule.setRules(Collections.singletonList(rule)); + + MockServerHttpRequest request = MockServerHttpRequest.get("/users") + .queryParam("source", "wx") + .header("uid", "1000").build(); + MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); + + RuleStainingExecutor executor = new RuleStainingExecutor(); + + Map stainedLabels = executor.execute(exchange, stainingRule); + + Assert.assertNotNull(stainedLabels); + Assert.assertEquals(1, stainedLabels.size()); + Assert.assertEquals("blue", stainedLabels.get("env")); + } + + @Test + public void testNotMatchCondition() { + Condition condition1 = new Condition(); + condition1.setKey("${http.header.uid}"); + condition1.setOperation(Operation.EQUAL.toString()); + condition1.setValues(Collections.singletonList("1000")); + + Condition condition2 = new Condition(); + condition2.setKey("${http.query.source}"); + condition2.setOperation(Operation.IN.toString()); + condition2.setValues(Collections.singletonList("wx")); + + StainingRule.Rule rule = new StainingRule.Rule(); + rule.setConditions(Arrays.asList(condition1, condition2)); + + KVPair kvPair = new KVPair(); + kvPair.setKey("env"); + kvPair.setValue("blue"); + rule.setLabels(Collections.singletonList(kvPair)); + + StainingRule stainingRule = new StainingRule(); + stainingRule.setRules(Collections.singletonList(rule)); + + MockServerHttpRequest request = MockServerHttpRequest.get("/users") + .queryParam("source", "wx") + .header("uid", "10001").build(); + MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); + + RuleStainingExecutor executor = new RuleStainingExecutor(); + + Map stainedLabels = executor.execute(exchange, stainingRule); + + Assert.assertNotNull(stainedLabels); + Assert.assertEquals(0, stainedLabels.size()); + } + + @Test + public void testMatchTwoRulesAndNotMatchOneRule() { + Condition condition1 = new Condition(); + condition1.setKey("${http.header.uid}"); + condition1.setOperation(Operation.EQUAL.toString()); + condition1.setValues(Collections.singletonList("1000")); + + Condition condition2 = new Condition(); + condition2.setKey("${http.query.source}"); + condition2.setOperation(Operation.IN.toString()); + condition2.setValues(Collections.singletonList("wx")); + + // rule1 matched + StainingRule.Rule rule1 = new StainingRule.Rule(); + rule1.setConditions(Arrays.asList(condition1, condition2)); + + KVPair kvPair = new KVPair(); + kvPair.setKey("env"); + kvPair.setValue("blue"); + rule1.setLabels(Collections.singletonList(kvPair)); + + // rule2 matched + StainingRule.Rule rule2 = new StainingRule.Rule(); + rule2.setConditions(Collections.singletonList(condition1)); + + KVPair kvPair2 = new KVPair(); + kvPair2.setKey("label1"); + kvPair2.setValue("value1"); + KVPair kvPair3 = new KVPair(); + kvPair3.setKey("label2"); + kvPair3.setValue("value2"); + rule2.setLabels(Arrays.asList(kvPair2, kvPair3)); + + // rule3 not matched + Condition condition3 = new Condition(); + condition3.setKey("${http.query.type}"); + condition3.setOperation(Operation.IN.toString()); + condition3.setValues(Collections.singletonList("wx")); + + StainingRule.Rule rule3 = new StainingRule.Rule(); + rule3.setConditions(Collections.singletonList(condition3)); + + KVPair kvPair4 = new KVPair(); + kvPair4.setKey("label3"); + kvPair4.setValue("value3"); + rule3.setLabels(Collections.singletonList(kvPair4)); + + StainingRule stainingRule = new StainingRule(); + stainingRule.setRules(Arrays.asList(rule1, rule2, rule3)); + + MockServerHttpRequest request = MockServerHttpRequest.get("/users") + .queryParam("source", "wx") + .header("uid", "1000").build(); + MockServerWebExchange exchange = new MockServerWebExchange.Builder(request).build(); + + RuleStainingExecutor executor = new RuleStainingExecutor(); + + Map stainedLabels = executor.execute(exchange, stainingRule); + + Assert.assertNotNull(stainedLabels); + Assert.assertEquals(3, stainedLabels.size()); + Assert.assertEquals("blue", stainedLabels.get("env")); + Assert.assertEquals("value1", stainedLabels.get("label1")); + Assert.assertEquals("value2", stainedLabels.get("label2")); + } +} diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java new file mode 100644 index 000000000..1ac88e3c8 --- /dev/null +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-gateway-plugin/src/test/java/com/tencent/cloud/plugin/gateway/staining/rule/StainingRuleManagerTest.java @@ -0,0 +1,133 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.plugin.gateway.staining.rule; + +import com.tencent.polaris.configuration.api.core.ConfigFile; +import com.tencent.polaris.configuration.api.core.ConfigFileService; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.when; + + +/** + * Test for {@link StainingRuleManager}. + * @author lepdou 2022-07-12 + */ +@RunWith(MockitoJUnitRunner.class) +public class StainingRuleManagerTest { + + @Mock + private ConfigFileService configFileService; + + private final String testNamespace = "testNamespace"; + private final String testGroup = "testGroup"; + private final String testFileName = "rule.json"; + + @Test + public void testNormalRule() { + RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); + ruleStainingProperties.setNamespace(testNamespace); + ruleStainingProperties.setGroup(testGroup); + ruleStainingProperties.setFileName(testFileName); + + ConfigFile configFile = Mockito.mock(ConfigFile.class); + when(configFile.getContent()).thenReturn("{\n" + + " \"rules\":[\n" + + " {\n" + + " \"conditions\":[\n" + + " {\n" + + " \"key\":\"${http.query.uid}\",\n" + + " \"values\":[\"1000\"],\n" + + " \"operation\":\"EQUAL\"\n" + + " }\n" + + " ],\n" + + " \"labels\":[\n" + + " {\n" + + " \"key\":\"env\",\n" + + " \"value\":\"blue\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"); + when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); + + StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); + + StainingRule stainingRule = stainingRuleManager.getStainingRule(); + + Assert.assertNotNull(stainingRule); + Assert.assertEquals(1, stainingRule.getRules().size()); + StainingRule.Rule rule = stainingRule.getRules().get(0); + Assert.assertEquals(1, rule.getConditions().size()); + Assert.assertEquals(1, rule.getLabels().size()); + } + + @Test(expected = RuntimeException.class) + public void testWrongRule() { + RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); + ruleStainingProperties.setNamespace(testNamespace); + ruleStainingProperties.setGroup(testGroup); + ruleStainingProperties.setFileName(testFileName); + + ConfigFile configFile = Mockito.mock(ConfigFile.class); + when(configFile.getContent()).thenReturn("{\n" + + " \"rules\":[\n" + + " {\n" + + " \"conditionsxxxx\":[\n" + + " {\n" + + " \"key\":\"${http.query.uid}\",\n" + + " \"values\":[\"1000\"],\n" + + " \"operation\":\"EQUAL\"\n" + + " }\n" + + " ],\n" + + " \"labels\":[\n" + + " {\n" + + " \"key\":\"env\",\n" + + " \"value\":\"blue\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"); + when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); + + new StainingRuleManager(ruleStainingProperties, configFileService); + } + + @Test + public void testEmptyRule() { + RuleStainingProperties ruleStainingProperties = new RuleStainingProperties(); + ruleStainingProperties.setNamespace(testNamespace); + ruleStainingProperties.setGroup(testGroup); + ruleStainingProperties.setFileName(testFileName); + + ConfigFile configFile = Mockito.mock(ConfigFile.class); + when(configFile.getContent()).thenReturn(null); + when(configFileService.getConfigFile(testNamespace, testGroup, testFileName)).thenReturn(configFile); + + StainingRuleManager stainingRuleManager = new StainingRuleManager(ruleStainingProperties, configFileService); + Assert.assertNull(stainingRuleManager.getStainingRule()); + } +} From 31354f1ff43cb0dd3321bc61b8a4a42cf25025d9 Mon Sep 17 00:00:00 2001 From: DerekYRC <44155264+DerekYRC@users.noreply.github.com> Date: Fri, 15 Jul 2022 09:41:53 +0800 Subject: [PATCH 2/4] =?UTF-8?q?Optimize:=20add=20EncodeTransferMedataRestT?= =?UTF-8?q?emplateInterceptor=20to=20RestTemp=E2=80=A6=20(#434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + .../MetadataTransferAutoConfiguration.java | 74 ++++--------------- ...MetadataTransferAutoConfigurationTest.java | 55 +++++++++++++- 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b44658e9c..afa7f3428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413) - [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417) - [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428) +- [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/434) diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java index 6384fa3b5..fa14d9ae4 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java @@ -18,8 +18,9 @@ package com.tencent.cloud.metadata.config; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Map; import com.netflix.zuul.ZuulFilter; import com.tencent.cloud.common.constant.MetadataConstant; @@ -30,18 +31,15 @@ import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateIntercept import com.tencent.cloud.metadata.core.EncodeTransferMedataScgFilter; import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; -import org.springframework.util.CollectionUtils; import org.springframework.web.client.RestTemplate; import static javax.servlet.DispatcherType.ASYNC; @@ -143,9 +141,10 @@ public class MetadataTransferAutoConfiguration { */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(name = "org.springframework.web.client.RestTemplate") - protected static class MetadataTransferRestTemplateConfig implements ApplicationContextAware { + protected static class MetadataTransferRestTemplateConfig { - private ApplicationContext context; + @Autowired(required = false) + private List restTemplates = Collections.emptyList(); @Bean public EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor() { @@ -153,59 +152,12 @@ public class MetadataTransferAutoConfiguration { } @Bean - BeanPostProcessor encodeTransferMetadataRestTemplatePostProcessor( - EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) { - // Coping with multiple bean injection scenarios - Map beans = this.context.getBeansOfType(RestTemplate.class); - // If the restTemplate has been created when the - // MetadataRestTemplatePostProcessor Bean - // is initialized, then manually set the interceptor. - if (!CollectionUtils.isEmpty(beans)) { - for (RestTemplate restTemplate : beans.values()) { - List interceptors = restTemplate.getInterceptors(); - // Avoid setting interceptor repeatedly. - if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) { - interceptors.add(encodeTransferMedataRestTemplateInterceptor); - restTemplate.setInterceptors(interceptors); - } - } - } - return new EncodeTransferMetadataRestTemplatePostProcessor(encodeTransferMedataRestTemplateInterceptor); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.context = applicationContext; - } - - public static class EncodeTransferMetadataRestTemplatePostProcessor - implements BeanPostProcessor { - - private final EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor; - - EncodeTransferMetadataRestTemplatePostProcessor( - EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor) { - this.encodeTransferMedataRestTemplateInterceptor = encodeTransferMedataRestTemplateInterceptor; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - if (bean instanceof RestTemplate) { - RestTemplate restTemplate = (RestTemplate) bean; - List interceptors = restTemplate.getInterceptors(); - // Avoid setting interceptor repeatedly. - if (!interceptors.contains(encodeTransferMedataRestTemplateInterceptor)) { - interceptors.add(this.encodeTransferMedataRestTemplateInterceptor); - restTemplate.setInterceptors(interceptors); - } - } - return bean; - } + public SmartInitializingSingleton addEncodeTransferMedataInterceptorForRestTemplate(EncodeTransferMedataRestTemplateInterceptor interceptor) { + return () -> restTemplates.forEach(restTemplate -> { + List list = new ArrayList<>(restTemplate.getInterceptors()); + list.add(interceptor); + restTemplate.setInterceptors(list); + }); } } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java index 1e5579f90..6e56bd3e1 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java @@ -18,6 +18,12 @@ package com.tencent.cloud.metadata.config; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateInterceptor; import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulFilter; @@ -26,7 +32,12 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.web.client.RestTemplate; /** * Test for {@link MetadataTransferAutoConfiguration}. @@ -54,10 +65,6 @@ public class MetadataTransferAutoConfigurationTest { MetadataTransferAutoConfiguration.MetadataTransferRestTemplateConfig.class); Assertions.assertThat(context).hasSingleBean( EncodeTransferMedataRestTemplateInterceptor.class); - Assertions.assertThat(context).hasSingleBean( - MetadataTransferAutoConfiguration - .MetadataTransferRestTemplateConfig - .EncodeTransferMetadataRestTemplatePostProcessor.class); Assertions.assertThat(context).hasSingleBean( MetadataTransferAutoConfiguration.MetadataTransferZuulFilterConfig.class); Assertions.assertThat(context) @@ -67,4 +74,44 @@ public class MetadataTransferAutoConfigurationTest { Assertions.assertThat(context).hasSingleBean(GlobalFilter.class); }); } + + + @Test + public void test2() { + this.applicationContextRunner + .withConfiguration( + AutoConfigurations.of(MetadataTransferAutoConfiguration.class, RestTemplateConfiguration.class)) + .run(context -> { + Assertions.assertThat(context) + .hasSingleBean(EncodeTransferMedataFeignInterceptor.class); + EncodeTransferMedataRestTemplateInterceptor encodeTransferMedataRestTemplateInterceptor = context.getBean(EncodeTransferMedataRestTemplateInterceptor.class); + Map restTemplateMap = context.getBeansOfType(RestTemplate.class); + Assertions.assertThat(restTemplateMap.size()).isEqualTo(2); + for (String beanName : Arrays.asList("restTemplate", "loadBalancedRestTemplate")) { + RestTemplate restTemplate = restTemplateMap.get(beanName); + Assertions.assertThat(restTemplate).isNotNull(); + List encodeTransferMedataFeignInterceptorList = restTemplate.getInterceptors() + .stream() + .filter(interceptor -> Objects.equals(interceptor, encodeTransferMedataRestTemplateInterceptor)) + .collect(Collectors.toList()); + //EncodeTransferMedataFeignInterceptor is not added repeatedly + Assertions.assertThat(encodeTransferMedataFeignInterceptorList.size()).isEqualTo(1); + } + }); + } + + @Configuration + static class RestTemplateConfiguration { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @LoadBalanced + @Bean + public RestTemplate loadBalancedRestTemplate() { + return new RestTemplate(); + } + } } From 4c3f6bba349a82bee401b55bfc0466c3b907c3b5 Mon Sep 17 00:00:00 2001 From: weihubeats Date: Fri, 15 Jul 2022 15:25:42 +0800 Subject: [PATCH 3/4] Optimized configuration refresh mechanism . (#423) --- CHANGELOG.md | 1 + .../PolarisConfigAutoConfiguration.java | 41 +++- ...larisConfigBootstrapAutoConfiguration.java | 16 ++ .../PolarisPropertySourceAutoRefresher.java | 101 ++++++++- .../SmartConfigurationPropertiesRebinder.java | 123 +++++++++++ .../ConditionalOnNonDefaultBehavior.java | 40 ++++ .../NonDefaultBehaviorCondition.java | 57 ++++++ .../polaris/config/enums/RefreshBehavior.java | 40 ++++ .../spring/annotation/PolarisProcessor.java | 73 +++++++ .../annotation/SpringValueProcessor.java | 169 +++++++++++++++ .../spring/property/PlaceholderHelper.java | 192 ++++++++++++++++++ .../config/spring/property/SpringValue.java | 153 ++++++++++++++ .../property/SpringValueDefinition.java | 51 +++++ .../SpringValueDefinitionProcessor.java | 124 +++++++++++ .../spring/property/SpringValueRegistry.java | 104 ++++++++++ ...itional-spring-configuration-metadata.json | 6 + .../config/adapter/MockedConfigChange.java | 19 ++ ...arisPropertiesSourceAutoRefresherTest.java | 67 +++--- .../cloud/common/util/JacksonUtils.java | 10 + .../src/main/resources/bootstrap.yml | 1 + 20 files changed, 1344 insertions(+), 44 deletions(-) create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java create mode 100644 spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java diff --git a/CHANGELOG.md b/CHANGELOG.md index afa7f3428..a16ddae21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,5 +3,6 @@ - [Bugfix: optimize ratelimit actuator](https://github.com/Tencent/spring-cloud-tencent/pull/413) - [Feature: add rate limit filter debug log](https://github.com/Tencent/spring-cloud-tencent/pull/417) +- [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/423) - [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428) - [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/434) diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java index fb70489c7..3976a7d01 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigAutoConfiguration.java @@ -20,13 +20,21 @@ package com.tencent.cloud.polaris.config; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceAutoRefresher; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder; import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor; +import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.config.listener.PolarisConfigChangeEventListener; +import com.tencent.cloud.polaris.config.spring.annotation.SpringValueProcessor; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -44,9 +52,10 @@ public class PolarisConfigAutoConfiguration { public PolarisPropertySourceAutoRefresher polarisPropertySourceAutoRefresher( PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager, - ContextRefresher contextRefresher) { + SpringValueRegistry springValueRegistry, + PlaceholderHelper placeholderHelper) { return new PolarisPropertySourceAutoRefresher(polarisConfigProperties, - polarisPropertySourceManager, contextRefresher); + polarisPropertySourceManager, springValueRegistry, placeholderHelper); } @Bean @@ -58,4 +67,30 @@ public class PolarisConfigAutoConfiguration { public PolarisConfigChangeEventListener polarisConfigChangeEventListener() { return new PolarisConfigChangeEventListener(); } + + @Bean + public SpringValueRegistry springValueRegistry() { + return new SpringValueRegistry(); + } + + @Bean + public PlaceholderHelper placeholderHelper() { + return new PlaceholderHelper(); + } + + @Bean + public SpringValueProcessor springValueProcessor(PlaceholderHelper placeholderHelper, SpringValueRegistry springValueRegistry, PolarisConfigProperties polarisConfigProperties) { + return new SpringValueProcessor(placeholderHelper, springValueRegistry, polarisConfigProperties); + } + + @Bean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) + @ConditionalOnNonDefaultBehavior + public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder( + ConfigurationPropertiesBeans beans) { + // If using default behavior, not use SmartConfigurationPropertiesRebinder. + // Minimize te possibility of making mistakes. + return new SmartConfigurationPropertiesRebinder(beans); + } + } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java index a67c8a89d..7c7afdf87 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/PolarisConfigBootstrapAutoConfiguration.java @@ -19,6 +19,8 @@ package com.tencent.cloud.polaris.config; import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator; import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager; +import com.tencent.cloud.polaris.config.adapter.SmartConfigurationPropertiesRebinder; +import com.tencent.cloud.polaris.config.condition.ConditionalOnNonDefaultBehavior; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration; @@ -27,7 +29,11 @@ import com.tencent.polaris.client.api.SDKContext; import com.tencent.polaris.configuration.api.core.ConfigFileService; import com.tencent.polaris.configuration.factory.ConfigFileServiceFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -79,4 +85,14 @@ public class PolarisConfigBootstrapAutoConfiguration { PolarisContextProperties polarisContextProperties) { return new ConfigurationModifier(polarisConfigProperties, polarisContextProperties); } + + @Bean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) + @ConditionalOnNonDefaultBehavior + public ConfigurationPropertiesRebinder smartConfigurationPropertiesRebinder( + ConfigurationPropertiesBeans beans) { + // If using default behavior, not use SmartConfigurationPropertiesRebinder. + // Minimize te possibility of making mistakes. + return new SmartConfigurationPropertiesRebinder(beans); + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java index 25af8ff32..fd70a8fa9 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertySourceAutoRefresher.java @@ -18,22 +18,32 @@ package com.tencent.cloud.polaris.config.adapter; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import com.tencent.cloud.common.util.JacksonUtils; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValue; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.util.CollectionUtils; /** @@ -43,29 +53,40 @@ import org.springframework.util.CollectionUtils; * @author lepdou 2022-03-28 */ public class PolarisPropertySourceAutoRefresher - implements ApplicationListener, ApplicationContextAware { + implements ApplicationListener, ApplicationContextAware, BeanFactoryAware { private static final Logger LOGGER = LoggerFactory.getLogger(PolarisPropertySourceAutoRefresher.class); private final PolarisConfigProperties polarisConfigProperties; private final PolarisPropertySourceManager polarisPropertySourceManager; - private final ContextRefresher contextRefresher; + private final AtomicBoolean registered = new AtomicBoolean(false); - private ApplicationContext applicationContext; + + private ConfigurableApplicationContext context; + + private TypeConverter typeConverter; + private final SpringValueRegistry springValueRegistry; + private ConfigurableBeanFactory beanFactory; + private final PlaceholderHelper placeholderHelper; + public PolarisPropertySourceAutoRefresher( PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager, - ContextRefresher contextRefresher) { + SpringValueRegistry springValueRegistry, + PlaceholderHelper placeholderHelper) { this.polarisConfigProperties = polarisConfigProperties; this.polarisPropertySourceManager = polarisPropertySourceManager; - this.contextRefresher = contextRefresher; + this.springValueRegistry = springValueRegistry; + this.placeholderHelper = placeholderHelper; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; + this.context = (ConfigurableApplicationContext) applicationContext; + this.beanFactory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); + this.typeConverter = this.beanFactory.getTypeConverter(); } @Override @@ -91,6 +112,7 @@ public class PolarisPropertySourceAutoRefresher for (PolarisPropertySource polarisPropertySource : polarisPropertySources) { polarisPropertySource.getConfigKVFile() .addChangeListener((ConfigKVFileChangeListener) configKVFileChangeEvent -> { + LOGGER.info( "[SCT Config] received polaris config change event and will refresh spring context." + "namespace = {}, group = {}, fileName = {}", @@ -115,11 +137,70 @@ public class PolarisPropertySourceAutoRefresher source.remove(changedKey); break; } - } - // rebuild beans with @RefreshScope annotation - contextRefresher.refresh(); + Collection targetValues = springValueRegistry.get(beanFactory, changedKey); + if (targetValues == null || targetValues.isEmpty()) { + continue; + } + // update the value + for (SpringValue val : targetValues) { + updateSpringValue(val); + } + + } + context.publishEvent(new EnvironmentChangeEvent(context, configKVFileChangeEvent.changedKeys())); }); } } + + private void updateSpringValue(SpringValue springValue) { + try { + Object value = resolvePropertyValue(springValue); + springValue.update(value); + + LOGGER.info("Auto update polaris changed value successfully, new value: {}, {}", value, + springValue); + } + catch (Throwable ex) { + LOGGER.error("Auto update polaris changed value failed, {}", springValue.toString(), ex); + } + } + + + /** + * Logic transplanted from DefaultListableBeanFactory. + * + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, + * java.lang.String, java.util.Set, org.springframework.beans.TypeConverter) + */ + private Object resolvePropertyValue(SpringValue springValue) { + // value will never be null + Object value = placeholderHelper + .resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder()); + + if (springValue.isJson()) { + value = parseJsonValue((String) value, springValue.getTargetType()); + } + else { + value = springValue.isField() ? this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), springValue.getField()) : + this.typeConverter.convertIfNecessary(value, springValue.getTargetType(), + springValue.getMethodParameter()); + } + return value; + } + + private Object parseJsonValue(String json, Class targetType) { + try { + return JacksonUtils.json2JavaBean(json, targetType); + } + catch (Throwable ex) { + LOGGER.error("Parsing json '{}' to type {} failed!", json, targetType, ex); + throw ex; + } + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + } } diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java new file mode 100644 index 000000000..bc275482c --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/adapter/SmartConfigurationPropertiesRebinder.java @@ -0,0 +1,123 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.config.adapter; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import com.tencent.cloud.polaris.config.enums.RefreshBehavior; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.properties.ConfigurationPropertiesBean; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.context.properties.ConfigurationPropertiesBeans; +import org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ReflectionUtils; + +import static com.tencent.cloud.polaris.config.condition.NonDefaultBehaviorCondition.POLARIS_CONFIG_REFRESH_BEHAVIOR; +import static com.tencent.cloud.polaris.config.enums.RefreshBehavior.ALL_BEANS; + +/** + * Extend {@link ConfigurationPropertiesRebinder}. + *

+ * Spring team doesn't seem to support single {@link ConfigurationPropertiesBean} refresh. + *

+ * SmartConfigurationPropertiesRebinder can refresh specific + * {@link ConfigurationPropertiesBean} base on the change keys. + *

+ * NOTE: We still use Spring's default behavior (full refresh) as default + * behavior, This feature can be considered an advanced feature, it may not be as stable + * as the default behavior. + * + * SmartConfigurationPropertiesRebinder + * + * @author weihubeats 2022-7-10 + */ +public class SmartConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder { + + private static final String BEANS = "beans"; + + private Map beanMap; + + private ApplicationContext applicationContext; + + private RefreshBehavior refreshBehavior; + + public SmartConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) { + super(beans); + fillBeanMap(beans); + } + + @SuppressWarnings("unchecked") + private void fillBeanMap(ConfigurationPropertiesBeans beans) { + this.beanMap = new HashMap<>(); + Field field = ReflectionUtils.findField(beans.getClass(), BEANS); + if (field != null) { + field.setAccessible(true); + this.beanMap.putAll((Map) Optional + .ofNullable(ReflectionUtils.getField(field, beans)) + .orElse(Collections.emptyMap())); + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + super.setApplicationContext(applicationContext); + this.applicationContext = applicationContext; + this.refreshBehavior = this.applicationContext.getEnvironment().getProperty( + POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class, + ALL_BEANS); + } + + @Override + public void onApplicationEvent(EnvironmentChangeEvent event) { + if (this.applicationContext.equals(event.getSource()) + // Backwards compatible + || event.getKeys().equals(event.getSource())) { + switch (refreshBehavior) { + case SPECIFIC_BEAN: + rebindSpecificBean(event); + break; + default: + rebind(); + break; + } + } + } + + private void rebindSpecificBean(EnvironmentChangeEvent event) { + Set refreshedSet = new HashSet<>(); + beanMap.forEach((name, bean) -> event.getKeys().forEach(changeKey -> { + String prefix = AnnotationUtils.getValue(bean.getAnnotation()).toString(); + // prevent multiple refresh one ConfigurationPropertiesBean. + if (changeKey.startsWith(prefix) && refreshedSet.add(name)) { + rebind(name); + } + })); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java new file mode 100644 index 000000000..d799ec28b --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/ConditionalOnNonDefaultBehavior.java @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.config.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Conditional; + +/** + * custom annotation. + * + * @author weihubeats 2022-7-13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(NonDefaultBehaviorCondition.class) +public @interface ConditionalOnNonDefaultBehavior { + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java new file mode 100644 index 000000000..98b14318e --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/condition/NonDefaultBehaviorCondition.java @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + +package com.tencent.cloud.polaris.config.condition; + +import com.tencent.cloud.polaris.config.enums.RefreshBehavior; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Extend SpringBootCondition. + * + * @author weihubeats 2022-7-13 + */ +public class NonDefaultBehaviorCondition extends SpringBootCondition { + + /** + * refresh behavior config. + */ + public static final String POLARIS_CONFIG_REFRESH_BEHAVIOR = "spring.cloud.polaris.config.refresh-behavior"; + + /** + * refresh behavior config default value. + */ + private static final RefreshBehavior DEFAULT_REFRESH_BEHAVIOR = RefreshBehavior.ALL_BEANS; + + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + RefreshBehavior behavior = context.getEnvironment().getProperty( + POLARIS_CONFIG_REFRESH_BEHAVIOR, RefreshBehavior.class, + DEFAULT_REFRESH_BEHAVIOR); + if (DEFAULT_REFRESH_BEHAVIOR == behavior) { + return ConditionOutcome.noMatch("no matched"); + } + return ConditionOutcome.match("matched"); + } + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java new file mode 100644 index 000000000..12abd79cb --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/enums/RefreshBehavior.java @@ -0,0 +1,40 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + */ + + +package com.tencent.cloud.polaris.config.enums; + +import org.springframework.boot.context.properties.ConfigurationPropertiesBean; + +/** + * Refresh behavior. + * + * @author weihubeats 2022-7-13 + */ +public enum RefreshBehavior { + + /** + * Refresh all {@link ConfigurationPropertiesBean}s. + */ + ALL_BEANS, + /** + * Refresh specific {@link ConfigurationPropertiesBean} base on change key. + */ + SPECIFIC_BEAN, + +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java new file mode 100644 index 000000000..3058e5eb8 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/PolarisProcessor.java @@ -0,0 +1,73 @@ +package com.tencent.cloud.polaris.config.spring.annotation; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; +import org.springframework.util.ReflectionUtils; + +/** + * Get spring bean properties and methods. + * + * @author weihubeats 2022-7-10 + */ +public abstract class PolarisProcessor implements BeanPostProcessor, PriorityOrdered { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + Class clazz = bean.getClass(); + for (Field field : findAllField(clazz)) { + processField(bean, beanName, field); + } + for (Method method : findAllMethod(clazz)) { + processMethod(bean, beanName, method); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + /** + * subclass should implement this method to process field. + * @param bean bean + * @param beanName beanName + * @param field field + */ + protected abstract void processField(Object bean, String beanName, Field field); + + /** + * subclass should implement this method to process method. + * @param bean bean + * @param beanName beanName + * @param method method + */ + protected abstract void processMethod(Object bean, String beanName, Method method); + + + @Override + public int getOrder() { + //make it as late as possible + return Ordered.LOWEST_PRECEDENCE; + } + + private List findAllField(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithFields(clazz, field -> res.add(field)); + return res; + } + + private List findAllMethod(Class clazz) { + final List res = new LinkedList<>(); + ReflectionUtils.doWithMethods(clazz, method -> res.add(method)); + return res; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java new file mode 100644 index 000000000..90e83f080 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java @@ -0,0 +1,169 @@ +package com.tencent.cloud.polaris.config.spring.annotation; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValue; +import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinition; +import com.tencent.cloud.polaris.config.spring.property.SpringValueDefinitionProcessor; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.Bean; + +/** + * Spring value processor of field or method which has @Value and xml config placeholders. + * + * SpringValueProcessor + * + * @author weihubeats 2022-7-10 + */ +public class SpringValueProcessor extends PolarisProcessor implements BeanFactoryPostProcessor, BeanFactoryAware { + + private static final Logger LOGGER = LoggerFactory.getLogger(SpringValueProcessor.class); + + private final PolarisConfigProperties polarisConfigProperties; + private final PlaceholderHelper placeholderHelper; + private final SpringValueRegistry springValueRegistry; + + private BeanFactory beanFactory; + private Multimap beanName2SpringValueDefinitions; + + public SpringValueProcessor(PlaceholderHelper placeholderHelper, + SpringValueRegistry springValueRegistry, + PolarisConfigProperties polarisConfigProperties) { + this.placeholderHelper = placeholderHelper; + this.polarisConfigProperties = polarisConfigProperties; + this.springValueRegistry = springValueRegistry; + beanName2SpringValueDefinitions = LinkedListMultimap.create(); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + if (polarisConfigProperties.isAutoRefresh() && beanFactory instanceof BeanDefinitionRegistry) { + beanName2SpringValueDefinitions = SpringValueDefinitionProcessor + .getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory); + } + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (polarisConfigProperties.isAutoRefresh()) { + super.postProcessBeforeInitialization(bean, beanName); + processBeanPropertyValues(bean, beanName); + } + return bean; + } + + + @Override + protected void processField(Object bean, String beanName, Field field) { + // register @Value on field + Value value = field.getAnnotation(Value.class); + if (value == null) { + return; + } + + doRegister(bean, beanName, field, value); + } + + @Override + protected void processMethod(Object bean, String beanName, Method method) { + //register @Value on method + Value value = method.getAnnotation(Value.class); + if (value == null) { + return; + } + //skip Configuration bean methods + if (method.getAnnotation(Bean.class) != null) { + return; + } + if (method.getParameterTypes().length != 1) { + LOGGER.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters", + bean.getClass().getName(), method.getName(), method.getParameterTypes().length); + return; + } + + doRegister(bean, beanName, method, value); + } + + private void doRegister(Object bean, String beanName, Member member, Value value) { + Set keys = placeholderHelper.extractPlaceholderKeys(value.value()); + if (keys.isEmpty()) { + return; + } + + for (String key : keys) { + SpringValue springValue; + if (member instanceof Field) { + Field field = (Field) member; + springValue = new SpringValue(key, value.value(), bean, beanName, field, false); + } + else if (member instanceof Method) { + Method method = (Method) member; + springValue = new SpringValue(key, value.value(), bean, beanName, method, false); + } + else { + LOGGER.error("Polaris @Value annotation currently only support to be used on methods and fields, " + + "but is used on {}", member.getClass()); + return; + } + springValueRegistry.register(beanFactory, key, springValue); + LOGGER.info("Monitoring {}", springValue); + } + } + + private void processBeanPropertyValues(Object bean, String beanName) { + Collection propertySpringValues = beanName2SpringValueDefinitions + .get(beanName); + if (propertySpringValues == null || propertySpringValues.isEmpty()) { + return; + } + + for (SpringValueDefinition definition : propertySpringValues) { + try { + PropertyDescriptor pd = BeanUtils + .getPropertyDescriptor(bean.getClass(), definition.getPropertyName()); + Method method = pd.getWriteMethod(); + if (method == null) { + continue; + } + SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(), + bean, beanName, method, false); + springValueRegistry.register(beanFactory, definition.getKey(), springValue); + LOGGER.debug("Monitoring {}", springValue); + } + catch (Throwable ex) { + LOGGER.error("Failed to enable auto update feature for {}.{}", bean.getClass(), + definition.getPropertyName()); + } + } + + // clear + beanName2SpringValueDefinitions.removeAll(beanName); + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java new file mode 100644 index 000000000..0caf41b1d --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java @@ -0,0 +1,192 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.config.spring.property; + +import java.util.Set; +import java.util.Stack; + +import com.google.common.base.Strings; +import com.google.common.collect.Sets; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanExpressionContext; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.util.StringUtils; + +/** + * Placeholder helper functions. + * + * PlaceholderHelper + * + * @author weihubeats 2022-7-10 + */ +public class PlaceholderHelper { + + private static final String PLACEHOLDER_PREFIX = "${"; + private static final String PLACEHOLDER_SUFFIX = "}"; + private static final String VALUE_SEPARATOR = ":"; + private static final String SIMPLE_PLACEHOLDER_PREFIX = "{"; + private static final String EXPRESSION_PREFIX = "#{"; + private static final String EXPRESSION_SUFFIX = "}"; + + /** + * Resolve placeholder property values, e.g. + * @param beanFactory beanFactory + * @param beanName beanName + * @param placeholder placeholder + * @return "${somePropertyValue}" -> "the actual property value" + */ + public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) { + // resolve string value + String strVal = beanFactory.resolveEmbeddedValue(placeholder); + + BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory + .getMergedBeanDefinition(beanName) : null); + + // resolve expressions like "#{systemProperties.myProp}" + return evaluateBeanDefinitionString(beanFactory, strVal, bd); + } + + private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value, + BeanDefinition beanDefinition) { + if (beanFactory.getBeanExpressionResolver() == null) { + return value; + } + Scope scope = (beanDefinition != null ? beanFactory + .getRegisteredScope(beanDefinition.getScope()) : null); + return beanFactory.getBeanExpressionResolver() + .evaluate(value, new BeanExpressionContext(beanFactory, scope)); + } + + /** + * + * @param propertyString propertyString + * @return + * Extract keys from placeholder, e.g. + *

  • ${some.key} => "some.key"
  • + *
  • ${some.key:${some.other.key:100}} => "some.key", "some.other.key"
  • + *
  • ${${some.key}} => "some.key"
  • + *
  • ${${some.key:other.key}} => "some.key"
  • + *
  • ${${some.key}:${another.key}} => "some.key", "another.key"
  • + *
  • #{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"
  • + */ + public Set extractPlaceholderKeys(String propertyString) { + Set placeholderKeys = Sets.newHashSet(); + + if (Strings.isNullOrEmpty(propertyString) || (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString))) { + return placeholderKeys; + } + + Stack stack = new Stack<>(); + stack.push(propertyString); + + while (!stack.isEmpty()) { + String strVal = stack.pop(); + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + if (startIndex == -1) { + placeholderKeys.add(strVal); + continue; + } + int endIndex = findPlaceholderEndIndex(strVal, startIndex); + if (endIndex == -1) { + // invalid placeholder? + continue; + } + + String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); + + // ${some.key:other.key} + if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) { + stack.push(placeholderCandidate); + } + else { + // some.key:${some.other.key:100} + int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR); + + if (separatorIndex == -1) { + stack.push(placeholderCandidate); + } + else { + stack.push(placeholderCandidate.substring(0, separatorIndex)); + String defaultValuePart = + normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length())); + if (!Strings.isNullOrEmpty(defaultValuePart)) { + stack.push(defaultValuePart); + } + } + } + + // has remaining part, e.g. ${a}.${b} + if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1) { + String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length())); + if (!Strings.isNullOrEmpty(remainingPart)) { + stack.push(remainingPart); + } + } + } + + return placeholderKeys; + } + + private boolean isNormalizedPlaceholder(String propertyString) { + return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX); + } + + private boolean isExpressionWithPlaceholder(String propertyString) { + return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.contains(EXPRESSION_SUFFIX) + && propertyString.contains(PLACEHOLDER_PREFIX) && propertyString.contains(PLACEHOLDER_SUFFIX); + } + + private String normalizeToPlaceholder(String strVal) { + int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX); + if (startIndex == -1) { + return null; + } + int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX); + if (endIndex == -1) { + return null; + } + + return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length()); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + PLACEHOLDER_PREFIX.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + PLACEHOLDER_SUFFIX.length(); + } + else { + return index; + } + } + else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) { + withinNestedPlaceholder++; + index = index + SIMPLE_PLACEHOLDER_PREFIX.length(); + } + else { + index++; + } + } + return -1; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java new file mode 100644 index 000000000..501c58eef --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java @@ -0,0 +1,153 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.config.spring.property; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import org.springframework.core.MethodParameter; + +/** + * Spring @Value method info. + * + * SpringValue + * + * @author weihubeats 2022-7-10 + */ +public class SpringValue { + + private MethodParameter methodParameter; + private Field field; + private final WeakReference beanRef; + private final String beanName; + private final String key; + private final String placeholder; + private final Class targetType; + private Type genericType; + private final boolean isJson; + + public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) { + this.beanRef = new WeakReference<>(bean); + this.beanName = beanName; + this.field = field; + this.key = key; + this.placeholder = placeholder; + this.targetType = field.getType(); + this.isJson = isJson; + if (isJson) { + this.genericType = field.getGenericType(); + } + } + + public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) { + this.beanRef = new WeakReference<>(bean); + this.beanName = beanName; + this.methodParameter = new MethodParameter(method, 0); + this.key = key; + this.placeholder = placeholder; + Class[] paramTps = method.getParameterTypes(); + this.targetType = paramTps[0]; + this.isJson = isJson; + if (isJson) { + this.genericType = method.getGenericParameterTypes()[0]; + } + } + + public void update(Object newVal) throws IllegalAccessException, InvocationTargetException { + if (isField()) { + injectField(newVal); + } + else { + injectMethod(newVal); + } + } + + private void injectField(Object newVal) throws IllegalAccessException { + Object bean = beanRef.get(); + if (bean == null) { + return; + } + boolean accessible = field.isAccessible(); + field.setAccessible(true); + field.set(bean, newVal); + field.setAccessible(accessible); + } + + private void injectMethod(Object newVal) + throws InvocationTargetException, IllegalAccessException { + Object bean = beanRef.get(); + if (bean == null) { + return; + } + methodParameter.getMethod().invoke(bean, newVal); + } + + public String getBeanName() { + return beanName; + } + + public Class getTargetType() { + return targetType; + } + + public String getPlaceholder() { + return this.placeholder; + } + + public MethodParameter getMethodParameter() { + return methodParameter; + } + + public boolean isField() { + return this.field != null; + } + + public Field getField() { + return field; + } + + public Type getGenericType() { + return genericType; + } + + public boolean isJson() { + return isJson; + } + + boolean isTargetBeanValid() { + return beanRef.get() != null; + } + + @Override + public String toString() { + Object bean = beanRef.get(); + if (bean == null) { + return ""; + } + if (isField()) { + return String + .format("key: %s, beanName: %s, field: %s.%s", key, beanName, bean.getClass() + .getName(), field.getName()); + } + return String.format("key: %s, beanName: %s, method: %s.%s", key, beanName, bean.getClass().getName(), + methodParameter.getMethod().getName()); + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java new file mode 100644 index 000000000..4f73edb77 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java @@ -0,0 +1,51 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.config.spring.property; + +/** + * Spring value. + * + * SpringValueDefinition + * + * @author weihubeats 2022-7-10 + */ + +public class SpringValueDefinition { + + private final String key; + private final String placeholder; + private final String propertyName; + + public SpringValueDefinition(String key, String placeholder, String propertyName) { + this.key = key; + this.placeholder = placeholder; + this.propertyName = propertyName; + } + + public String getKey() { + return key; + } + + public String getPlaceholder() { + return placeholder; + } + + public String getPropertyName() { + return propertyName; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java new file mode 100644 index 000000000..4b814266a --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java @@ -0,0 +1,124 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.config.spring.property; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; + +import org.springframework.beans.BeansException; +import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; + +/** + * To process xml config placeholders, e.g. + * + *
    + *  <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
    + *    <property name="timeout" value="${timeout:200}"/>
    + *    <property name="batch" value="${batch:100}"/>
    + *  </bean>
    + * 
    + * + * + * SpringValueDefinitionProcessor + * + * @author weihubeats 2022-7-10 + */ +public class SpringValueDefinitionProcessor implements BeanDefinitionRegistryPostProcessor { + private static final Map> beanName2SpringValueDefinitions = + Maps.newConcurrentMap(); + private static final Set PROPERTY_VALUES_PROCESSED_BEAN_FACTORIES = Sets.newConcurrentHashSet(); + + private final PlaceholderHelper placeholderHelper; + + private PolarisConfigProperties polarisConfigProperties; + + public SpringValueDefinitionProcessor(PlaceholderHelper placeholderHelper, PolarisConfigProperties polarisConfigProperties) { + this.polarisConfigProperties = polarisConfigProperties; + this.placeholderHelper = placeholderHelper; + } + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { + if (polarisConfigProperties.isAutoRefresh()) { + processPropertyValues(registry); + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + + } + + public static Multimap getBeanName2SpringValueDefinitions(BeanDefinitionRegistry registry) { + Multimap springValueDefinitions = beanName2SpringValueDefinitions.get(registry); + if (springValueDefinitions == null) { + springValueDefinitions = LinkedListMultimap.create(); + } + + return springValueDefinitions; + } + + private void processPropertyValues(BeanDefinitionRegistry beanRegistry) { + if (!PROPERTY_VALUES_PROCESSED_BEAN_FACTORIES.add(beanRegistry)) { + // already initialized + return; + } + + if (!beanName2SpringValueDefinitions.containsKey(beanRegistry)) { + beanName2SpringValueDefinitions.put(beanRegistry, LinkedListMultimap.create()); + } + + Multimap springValueDefinitions = beanName2SpringValueDefinitions.get(beanRegistry); + + String[] beanNames = beanRegistry.getBeanDefinitionNames(); + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanRegistry.getBeanDefinition(beanName); + MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues(); + List propertyValues = mutablePropertyValues.getPropertyValueList(); + for (PropertyValue propertyValue : propertyValues) { + Object value = propertyValue.getValue(); + if (!(value instanceof TypedStringValue)) { + continue; + } + String placeholder = ((TypedStringValue) value).getValue(); + Set keys = placeholderHelper.extractPlaceholderKeys(placeholder); + + if (keys.isEmpty()) { + continue; + } + + for (String key : keys) { + springValueDefinitions.put(beanName, new SpringValueDefinition(key, placeholder, propertyValue.getName())); + } + } + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java new file mode 100644 index 000000000..6bf09c770 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java @@ -0,0 +1,104 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.tencent.cloud.polaris.config.spring.property; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import com.tencent.polaris.client.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.BeanFactory; + +/** + * Spring value auto registry. + * + * SpringValueRegistry + * + * @author weihubeats 2022-7-10 + */ +public class SpringValueRegistry { + private static final Logger logger = LoggerFactory.getLogger(SpringValueRegistry.class); + + private static final long CLEAN_INTERVAL_IN_SECONDS = 5; + private final Map> registry = Maps.newConcurrentMap(); + private final AtomicBoolean initialized = new AtomicBoolean(false); + private final Object LOCK = new Object(); + + public void register(BeanFactory beanFactory, String key, SpringValue springValue) { + if (!registry.containsKey(beanFactory)) { + synchronized (LOCK) { + if (!registry.containsKey(beanFactory)) { + registry.put(beanFactory, Multimaps.synchronizedListMultimap(LinkedListMultimap.create())); + } + } + } + + registry.get(beanFactory).put(key, springValue); + + // lazy initialize + if (initialized.compareAndSet(false, true)) { + initialize(); + } + } + + public Collection get(BeanFactory beanFactory, String key) { + Multimap beanFactorySpringValues = registry.get(beanFactory); + if (beanFactorySpringValues == null) { + return null; + } + return beanFactorySpringValues.get(key); + } + + private void initialize() { + Executors.newSingleThreadScheduledExecutor( + new NamedThreadFactory("polaris-spring-value-registry")).scheduleAtFixedRate( + () -> { + try { + scanAndClean(); + } + catch (Throwable ex) { + logger.error(ex.getMessage(), ex); + } + }, CLEAN_INTERVAL_IN_SECONDS, CLEAN_INTERVAL_IN_SECONDS, TimeUnit.SECONDS); + } + + private void scanAndClean() { + Iterator> iterator = registry.values().iterator(); + while (!Thread.currentThread().isInterrupted() && iterator.hasNext()) { + Multimap springValues = iterator.next(); + Iterator> springValueIterator = springValues.entries().iterator(); + while (springValueIterator.hasNext()) { + Map.Entry springValue = springValueIterator.next(); + if (!springValue.getValue().isTargetBeanValid()) { + // clear unused spring values + springValueIterator.remove(); + } + } + } + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 9954a62ce..3de4948d4 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-starter-tencent-polaris-config/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -41,6 +41,12 @@ "defaultValue": "true", "description": "Whether to connect to a remote server, suitable for local development mode.", "sourceType": "com.tencent.cloud.polaris.config.config.PolarisConfigProperties" + }, + { + "name": "spring.cloud.polaris.config.refresh-behavior", + "type": "com.tencent.cloud.polaris.config.enums.RefreshBehavior", + "defaultValue": "all_beans", + "description": "ConfigurationPropertiesBean refresh behavior." } ] } diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java new file mode 100644 index 000000000..c1a20c9c0 --- /dev/null +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/MockedConfigChange.java @@ -0,0 +1,19 @@ +package com.tencent.cloud.polaris.config.adapter; + +/** + * Mock config kv file for test. + * + * @author weihubeats 2022-7-10 + */ +public class MockedConfigChange { + + private String k1; + + String getK1() { + return k1; + } + + void setK1(String k1) { + this.k1 = k1; + } +} diff --git a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java index 779ddfb47..d64fcf848 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java +++ b/spring-cloud-starter-tencent-polaris-config/src/test/java/com/tencent/cloud/polaris/config/adapter/PolarisPropertiesSourceAutoRefresherTest.java @@ -18,11 +18,17 @@ package com.tencent.cloud.polaris.config.adapter; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import com.google.common.collect.Lists; import com.tencent.cloud.polaris.config.config.PolarisConfigProperties; +import com.tencent.cloud.polaris.config.spring.property.PlaceholderHelper; +import com.tencent.cloud.polaris.config.spring.property.SpringValue; +import com.tencent.cloud.polaris.config.spring.property.SpringValueRegistry; import com.tencent.polaris.configuration.api.core.ChangeType; import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent; import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo; @@ -32,9 +38,13 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.context.ConfigurableApplicationContext; -import static org.mockito.Mockito.verify; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** @@ -55,10 +65,33 @@ public class PolarisPropertiesSourceAutoRefresherTest { @Mock private ContextRefresher contextRefresher; + @Mock + private SpringValueRegistry springValueRegistry; + + @Mock + private PlaceholderHelper placeholderHelper; + @Test - public void testConfigFileChanged() { + public void testConfigFileChanged() throws Exception { PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties, - polarisPropertySourceManager, contextRefresher); + polarisPropertySourceManager, springValueRegistry, placeholderHelper); + + ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class); + ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class); + TypeConverter typeConverter = mock(TypeConverter.class); + when(beanFactory.getTypeConverter()).thenReturn(typeConverter); + when(applicationContext.getBeanFactory()).thenReturn(beanFactory); + refresher.setApplicationContext(applicationContext); + when(typeConverter.convertIfNecessary(any(), any(), (Field) any())).thenReturn("v11"); + Collection springValues = new ArrayList<>(); + MockedConfigChange mockedConfigChange = new MockedConfigChange(); + mockedConfigChange.setK1("v1"); + Field field = mockedConfigChange.getClass().getDeclaredField("k1"); + SpringValue springValue = new SpringValue("v1", "placeholder", mockedConfigChange, "mockedConfigChange", field, false); + + springValues.add(springValue); + + when(springValueRegistry.get(any(), any())).thenReturn(springValues); when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); @@ -89,33 +122,5 @@ public class PolarisPropertiesSourceAutoRefresherTest { Assert.assertEquals("v3", polarisPropertySource.getProperty("k3")); Assert.assertNull(polarisPropertySource.getProperty("k2")); Assert.assertEquals("v4", polarisPropertySource.getProperty("k4")); - verify(contextRefresher).refresh(); - } - - @Test - public void testNewConfigFile() { - PolarisPropertySourceAutoRefresher refresher = new PolarisPropertySourceAutoRefresher(polarisConfigProperties, - polarisPropertySourceManager, contextRefresher); - - when(polarisConfigProperties.isAutoRefresh()).thenReturn(true); - - Map emptyContent = new HashMap<>(); - MockedConfigKVFile file = new MockedConfigKVFile(emptyContent); - PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName, - file, emptyContent); - - when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource)); - - ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", null, "v1", ChangeType.ADDED); - Map changeInfos = new HashMap<>(); - changeInfos.put("k1", changeInfo); - - ConfigKVFileChangeEvent event = new ConfigKVFileChangeEvent(changeInfos); - refresher.onApplicationEvent(null); - - file.fireChangeListener(event); - - Assert.assertEquals("v1", polarisPropertySource.getProperty("k1")); - verify(contextRefresher).refresh(); } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java index c693fef26..b106909c6 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/JacksonUtils.java @@ -94,4 +94,14 @@ public final class JacksonUtils { throw new RuntimeException("Json to map failed.", e); } } + + public static T json2JavaBean(String content, Class valueType) { + try { + return OM.readValue(content, valueType); + } + catch (Exception e) { + LOG.error("json {} to class {} failed. ", content, valueType, e); + throw new RuntimeException("json to class failed.", e); + } + } } diff --git a/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml index 1f257a787..6a13aee32 100644 --- a/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-config-example/src/main/resources/bootstrap.yml @@ -12,6 +12,7 @@ spring: groups: - name: ${spring.application.name} # group name files: [ "config/application.properties", "config/bootstrap.yml" ] # config/application.properties takes precedence over config/bootstrap.yml + refresh-behavior: specific_bean management: endpoints: web: From 3e1d79d03316b7a16a6b674fce8d0b22503a1185 Mon Sep 17 00:00:00 2001 From: lepdou Date: Sat, 16 Jul 2022 16:04:20 +0800 Subject: [PATCH 4/4] Specification apollo code reference notes (#442) --- CHANGELOG.md | 1 + .../config/annotation/PolarisConfigAnnotationProcessor.java | 2 +- .../config/annotation/PolarisConfigKVFileChangeListener.java | 2 +- .../polaris/config/listener/PolarisConfigListenerContext.java | 2 +- .../polaris/config/spring/annotation/SpringValueProcessor.java | 3 +++ .../polaris/config/spring/property/PlaceholderHelper.java | 3 +++ .../cloud/polaris/config/spring/property/SpringValue.java | 3 +++ .../polaris/config/spring/property/SpringValueDefinition.java | 3 +++ .../config/spring/property/SpringValueDefinitionProcessor.java | 3 ++- .../polaris/config/spring/property/SpringValueRegistry.java | 3 +++ 10 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a16ddae21..44c0b887e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,4 @@ - [Feature: Optimized configuration update](https://github.com/Tencent/spring-cloud-tencent/pull/423) - [Feature: add feature-env plugin & add spring cloud gateway staining plugin](https://github.com/Tencent/spring-cloud-tencent/pull/428) - [Optimize: add EncodeTransferMedataRestTemplateInterceptor to RestTemplate](https://github.com/Tencent/spring-cloud-tencent/pull/434) +- [Optimize: Specification apollo code reference notes](https://github.com/Tencent/spring-cloud-tencent/pull/442) diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java index 59bbb994c..52704cc9b 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigAnnotationProcessor.java @@ -40,7 +40,7 @@ import static com.tencent.cloud.polaris.config.listener.PolarisConfigListenerCon /** * {@link PolarisConfigAnnotationProcessor} implementation for spring . - *

    Refer to the Apollo project implementation: + *

    This source file was reference from: * * ApolloAnnotationProcessor * @author Palmer Xu 2022-06-07 diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java index a324c4d78..d5e291996 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/annotation/PolarisConfigKVFileChangeListener.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; /** * Configuring the change listener annotation. - *

    Refer to the Apollo project implementation: + *

    This source file was reference from: * * ApolloAnnotationProcessor * @author Palmer Xu 2022-05-31 diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java index 15a9a9336..6bd266bf8 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/listener/PolarisConfigListenerContext.java @@ -49,7 +49,7 @@ import static com.tencent.polaris.configuration.api.core.ChangeType.MODIFIED; /** * Polaris Config Listener Context Defined . - *

    Refer to the Apollo project implementation: + *

    This source file was reference from: * * AbstractConfig * diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java index 90e83f080..d7d09912b 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/annotation/SpringValueProcessor.java @@ -30,6 +30,9 @@ import org.springframework.context.annotation.Bean; /** * Spring value processor of field or method which has @Value and xml config placeholders. + *
    + *
    + * This source file was originally from: * * SpringValueProcessor * diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java index 0caf41b1d..2d7515661 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/PlaceholderHelper.java @@ -31,6 +31,9 @@ import org.springframework.util.StringUtils; /** * Placeholder helper functions. + *
    + *
    + * This source file was originally from: * * PlaceholderHelper * diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java index 501c58eef..1f0396112 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValue.java @@ -27,6 +27,9 @@ import org.springframework.core.MethodParameter; /** * Spring @Value method info. + *
    + *
    + * This source file was originally from: * * SpringValue * diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java index 4f73edb77..a3382fd5d 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinition.java @@ -19,6 +19,9 @@ package com.tencent.cloud.polaris.config.spring.property; /** * Spring value. + *
    + *
    + * This source file was originally from: * * SpringValueDefinition * diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java index 4b814266a..56ed62391 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueDefinitionProcessor.java @@ -40,12 +40,13 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProce * To process xml config placeholders, e.g. * *

    - *  <bean class="com.ctrip.framework.apollo.demo.spring.xmlConfigDemo.bean.XmlBean">
    + *  <bean class="com.demo.bean.XmlBean">
      *    <property name="timeout" value="${timeout:200}"/>
      *    <property name="batch" value="${batch:100}"/>
      *  </bean>
      * 
    * + * This source file was originally from: * * SpringValueDefinitionProcessor * diff --git a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java index 6bf09c770..9155c923c 100644 --- a/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java +++ b/spring-cloud-starter-tencent-polaris-config/src/main/java/com/tencent/cloud/polaris/config/spring/property/SpringValueRegistry.java @@ -36,6 +36,9 @@ import org.springframework.beans.factory.BeanFactory; /** * Spring value auto registry. + *
    + *
    + * This source file was originally from: * * SpringValueRegistry *