hoxton 2.0.0.0 (#1458)

* fix:fix restTemplateCustomizer bean conflict causing service to fail to start properly.

* fix:fix NullPointerException when properties contain kv with null value.

* fix: memory not released while using wildcard api call with circuitbreaker (#1361)

* fix: memory not released while using wildcard api call with circuitbreaker enabled

* fix: change version 1.13.3-Hoxton.SR12-SNAPSHOT

* fix: update polaris verion to 1.15.9 release

* Update CHANGELOG.md

* release 1.13.3-Hoxton.SR12 (#1362)

* fix: memory not released while using wildcard api call with circuitbreaker enabled

* fix: change version 1.13.3-Hoxton.SR12-SNAPSHOT

* fix: update polaris verion to 1.15.9 release

* Update CHANGELOG.md

* release: 1.13.3-Hoxton.SR12

* fix: fix PolarisCircuitBreakerConfiguration not clear when gateway invoke by wildcard apis (#1392)

* fix: CHANGE VERSION TO 1.13.4-Hoxton.SR12-SNAPSHOT (#1407)

* fix: fix PolarisCircuitBreakerConfiguration not clear when gateway invoked by wildcard apis

* Update CHANGELOG.md

* fix: restore PolarisCircuitBreakerUtils and use ThreadPoolUtils.waitAndStopThreadPools instead

* fix: 修复命名不一致问题

* fix: CHANGE VERSION TO 1.13.4-Hoxton.SR12-SNAPSHOT

* Update polaris version to 1.15.10-SNAPSHOT

* fix:fix rate limit window update bug.

* fix: fix npe when feign.hystrix.enabled=false (#1436)

* feat: support 2.0.0 feature

* fix

* feat: support lossless config from console & support warmup (#1435)
feat:add admin http handler. (#1448)
feat:support concurrency rate limit. (#1454)

* fix

* add changelog

* fix

* fix changelog

* fix

* fix checkstyle

* fix ut

---------

Co-authored-by: 码匠君 <pointer_v@qq.com>
Co-authored-by: Haotian Zhang <928016560@qq.com>
Co-authored-by: andrew shan <45474304+andrewshan@users.noreply.github.com>
Co-authored-by: shedfreewu <shedfreewu@tencent.com>
hoxton
shedfreewu 18 hours ago committed by GitHub
parent eab382b371
commit 84a98de136
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -15,4 +15,5 @@
- [fix:fix the ratelimit bug for hoxton](https://github.com/Tencent/spring-cloud-tencent/pull/1301)
- [feat:upgrade jacoco version.](https://github.com/Tencent/spring-cloud-tencent/pull/1306)
- [fix:fix no registry when lossless is disabled.](https://github.com/Tencent/spring-cloud-tencent/pull/1313)
- [fix: memory not released while using wildcard api call with circuitbreaker enabled](https://github.com/Tencent/spring-cloud-tencent/pull/1335)
- [fix: memory not released while using wildcard api call with circuitbreaker enabled](https://github.com/Tencent/spring-cloud-tencent/pull/1335)
- [feat: support 2.0.0](https://github.com/Tencent/spring-cloud-tencent/pull/1458)

@ -90,7 +90,7 @@
<properties>
<!-- Project revision -->
<revision>1.14.0-Hoxton.SR12-RC3</revision>
<revision>2.0.0.0-Hoxton.SR12-SNAPSHOT</revision>
<!-- Spring Framework -->
<spring.framework.version>5.2.25.RELEASE</spring.framework.version>

@ -49,6 +49,11 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-contract</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-trace-plugin</artifactId>
</dependency>
</dependencies>
<build>

@ -26,7 +26,6 @@ import com.tencent.cloud.metadata.core.EncodeTransferMedataRestTemplateEnhancedP
import com.tencent.cloud.metadata.core.EncodeTransferMedataScgEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin;
import com.tencent.cloud.metadata.core.EncodeTransferMetadataZuulEnhancedPlugin;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -67,8 +66,8 @@ public class MetadataTransferAutoConfiguration {
}
@Bean
public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisContextProperties polarisContextProperties) {
return new DecodeTransferMetadataServletFilter(polarisContextProperties);
public DecodeTransferMetadataServletFilter metadataServletFilter() {
return new DecodeTransferMetadataServletFilter();
}
}
@ -80,8 +79,8 @@ public class MetadataTransferAutoConfiguration {
protected static class MetadataReactiveFilterConfig {
@Bean
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter(PolarisContextProperties polarisContextProperties) {
return new DecodeTransferMetadataReactiveFilter(polarisContextProperties);
public DecodeTransferMetadataReactiveFilter metadataReactiveFilter() {
return new DecodeTransferMetadataReactiveFilter();
}
}
@ -91,11 +90,10 @@ public class MetadataTransferAutoConfiguration {
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "com.netflix.zuul.http.ZuulServlet")
@ConditionalOnProperty(value = "spring.cloud.tencent.rpc-enhancement.enabled", havingValue = "true", matchIfMissing = true)
protected static class MetadataTransferZuulFilterConfig {
@Bean
public EncodeTransferMetadataZuulEnhancedPlugin encodeTransferMedataZuulEnhancedPlugin() {
public EncodeTransferMetadataZuulEnhancedPlugin encodeTransferMetadataZuulEnhancedPlugin() {
return new EncodeTransferMetadataZuulEnhancedPlugin();
}

@ -27,7 +27,7 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ReactiveMetadataProvider;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
@ -39,8 +39,10 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static com.tencent.polaris.metadata.core.constant.MetadataConstants.LOCAL_IP;
/**
* Filter used for storing the metadata from upstream temporarily when web application is
@ -50,14 +52,8 @@ import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUST
*/
public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered {
private PolarisContextProperties polarisContextProperties;
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataReactiveFilter.class);
public DecodeTransferMetadataReactiveFilter(PolarisContextProperties polarisContextProperties) {
this.polarisContextProperties = polarisContextProperties;
}
@Override
public int getOrder() {
return OrderConstant.Server.Reactive.DECODE_TRANSFER_METADATA_FILTER_ORDER;
@ -67,16 +63,34 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// Get metadata string from http header.
ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
Map<String, String> internalTransitiveMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_METADATA);
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
// transitive metadata
// from specific header
Map<String, String> internalTransitiveMetadata = getInternalMetadata(serverHttpRequest, CUSTOM_METADATA);
// from header with specific prefix
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(serverWebExchange);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
Map<String, String> internalDisposableMetadata = getIntervalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA);
// disposable metadata
// from specific header
Map<String, String> internalDisposableMetadata = getInternalMetadata(serverHttpRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
ReactiveMetadataProvider metadataProvider = new ReactiveMetadataProvider(serverHttpRequest, polarisContextProperties.getLocalIpAddress());
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider);
// application metadata
Map<String, String> internalApplicationMetadata = getInternalMetadata(serverHttpRequest, APPLICATION_METADATA);
Map<String, String> mergedApplicationMetadata = new HashMap<>(internalApplicationMetadata);
String callerIp = "";
if (StringUtils.isNotBlank(mergedApplicationMetadata.get(LOCAL_IP))) {
callerIp = mergedApplicationMetadata.get(LOCAL_IP);
}
// message metadata
ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, callerIp);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider);
// Save to ServerWebExchange.
serverWebExchange.getAttributes().put(
MetadataConstant.HeaderName.METADATA_CONTEXT,
@ -89,7 +103,7 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered
.doFinally((type) -> MetadataContextHolder.remove());
}
private Map<String, String> getIntervalMetadata(ServerHttpRequest serverHttpRequest, String headerName) {
private Map<String, String> getInternalMetadata(ServerHttpRequest serverHttpRequest, String headerName) {
HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
String customMetadataStr = UrlUtils.decode(httpHeaders.getFirst(headerName));
LOG.debug("Get upstream metadata string: {}", customMetadataStr);

@ -32,7 +32,7 @@ import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.metadata.provider.ServletMetadataProvider;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -40,8 +40,10 @@ import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static com.tencent.polaris.metadata.core.constant.MetadataConstants.LOCAL_IP;
/**
* Filter used for storing the metadata from upstream temporarily when web application is
@ -54,26 +56,36 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class);
private PolarisContextProperties polarisContextProperties;
public DecodeTransferMetadataServletFilter(PolarisContextProperties polarisContextProperties) {
this.polarisContextProperties = polarisContextProperties;
}
@Override
protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest,
@NonNull HttpServletResponse httpServletResponse, FilterChain filterChain)
throws ServletException, IOException {
// transitive metadata
// from specific header
Map<String, String> internalTransitiveMetadata = getInternalMetadata(httpServletRequest, CUSTOM_METADATA);
// from header with specific prefix
Map<String, String> customTransitiveMetadata = CustomTransitiveMetadataResolver.resolve(httpServletRequest);
Map<String, String> mergedTransitiveMetadata = new HashMap<>();
mergedTransitiveMetadata.putAll(internalTransitiveMetadata);
mergedTransitiveMetadata.putAll(customTransitiveMetadata);
// disposable metadata
// from specific header
Map<String, String> internalDisposableMetadata = getInternalMetadata(httpServletRequest, CUSTOM_DISPOSABLE_METADATA);
Map<String, String> mergedDisposableMetadata = new HashMap<>(internalDisposableMetadata);
ServletMetadataProvider metadataProvider = new ServletMetadataProvider(httpServletRequest, polarisContextProperties.getLocalIpAddress());
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, metadataProvider);
// application metadata
Map<String, String> internalApplicationMetadata = getInternalMetadata(httpServletRequest, APPLICATION_METADATA);
Map<String, String> mergedApplicationMetadata = new HashMap<>(internalApplicationMetadata);
String callerIp = "";
if (StringUtils.isNotBlank(mergedApplicationMetadata.get(LOCAL_IP))) {
callerIp = mergedApplicationMetadata.get(LOCAL_IP);
}
// message metadata
ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, callerIp);
MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider);
TransHeadersTransfer.transfer(httpServletRequest);
try {

@ -39,6 +39,7 @@ import feign.Request;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
@ -64,6 +65,7 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> applicationMetadata = metadataContext.getApplicationMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
@ -77,6 +79,9 @@ public class EncodeTransferMedataFeignEnhancedPlugin implements EnhancedPlugin {
// process custom metadata
this.buildMetadataHeader(request, customMetadata, CUSTOM_METADATA);
// add application metadata
this.buildMetadataHeader(request, applicationMetadata, APPLICATION_METADATA);
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(request, transHeaders);
}

@ -35,6 +35,7 @@ import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.http.HttpRequest;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
@ -60,6 +61,7 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> applicationMetadata = metadataContext.getApplicationMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
@ -72,6 +74,9 @@ public class EncodeTransferMedataRestTemplateEnhancedPlugin implements EnhancedP
// build custom metadata request header
this.buildMetadataHeader(httpRequest, customMetadata, CUSTOM_METADATA);
// build application metadata request header
this.buildMetadataHeader(httpRequest, applicationMetadata, APPLICATION_METADATA);
// set headers that need to be transmitted from the upstream
this.buildTransmittedHeader(httpRequest, transHeaders);
}

@ -37,6 +37,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
@ -69,6 +70,7 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> applicationMetadata = metadataContext.getApplicationMetadata();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
@ -77,6 +79,7 @@ public class EncodeTransferMedataScgEnhancedPlugin implements EnhancedPlugin {
this.buildMetadataHeader(builder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(builder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildMetadataHeader(builder, applicationMetadata, APPLICATION_METADATA);
TransHeadersTransfer.transfer(exchange.getRequest());
context.setOriginRequest(exchange.mutate().request(builder.build()).build());

@ -35,6 +35,7 @@ import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.ClientRequest;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
@ -59,6 +60,7 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug
MetadataContext metadataContext = MetadataContextHolder.get();
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> applicationMetadata = metadataContext.getApplicationMetadata();
Map<String, String> transHeaders = metadataContext.getTransHeadersKV();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
@ -70,6 +72,7 @@ public class EncodeTransferMedataWebClientEnhancedPlugin implements EnhancedPlug
this.buildMetadataHeader(requestBuilder, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(requestBuilder, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildMetadataHeader(requestBuilder, applicationMetadata, APPLICATION_METADATA);
this.buildTransmittedHeader(requestBuilder, transHeaders);
context.setOriginRequest(requestBuilder.build());

@ -18,9 +18,10 @@
package com.tencent.cloud.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import com.google.common.collect.ImmutableMap;
import com.netflix.zuul.context.RequestContext;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
@ -35,15 +36,14 @@ import com.tencent.polaris.metadata.core.MetadataType;
import org.springframework.util.CollectionUtils;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.APPLICATION_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
/**
* Pre EnhancedPlugin for zuul to encode transfer metadata.
*
* @author Shedfree Wu
*/
public class EncodeTransferMetadataZuulEnhancedPlugin implements EnhancedPlugin {
@Override
public EnhancedPluginType getType() {
return EnhancedPluginType.Client.PRE;
@ -54,6 +54,8 @@ public class EncodeTransferMetadataZuulEnhancedPlugin implements EnhancedPlugin
if (!(context.getOriginRequest() instanceof RequestContext)) {
return;
}
// get request context
RequestContext requestContext = (RequestContext) context.getOriginRequest();
// get metadata of current thread
@ -61,21 +63,26 @@ public class EncodeTransferMetadataZuulEnhancedPlugin implements EnhancedPlugin
Map<String, String> customMetadata = metadataContext.getCustomMetadata();
Map<String, String> disposableMetadata = metadataContext.getDisposableMetadata();
Map<String, String> applicationMetadata = metadataContext.getApplicationMetadata();
MessageMetadataContainer calleeMessageMetadataContainer = metadataContext.getMetadataContainer(MetadataType.MESSAGE, false);
Map<String, String> calleeTransitiveHeaders = calleeMessageMetadataContainer.getTransitiveHeaders();
// currently only support transitive header from calleeMessageMetadataContainer
this.buildHeaderMap(requestContext, calleeTransitiveHeaders);
// Rebuild Metadata Header
this.buildMetadataHeader(requestContext, customMetadata, CUSTOM_METADATA);
this.buildMetadataHeader(requestContext, disposableMetadata, CUSTOM_DISPOSABLE_METADATA);
this.buildMetadataHeader(requestContext, applicationMetadata, APPLICATION_METADATA);
TransHeadersTransfer.transfer(requestContext.getRequest());
}
private void buildHeaderMap(RequestContext context, Map<String, String> headerMap) {
private void buildHeaderMap(RequestContext requestContext, Map<String, String> headerMap) {
if (!CollectionUtils.isEmpty(headerMap)) {
headerMap.forEach((key, value) -> context.addZuulRequestHeader(key, UrlUtils.encode(value)));
headerMap.forEach((key, value) -> requestContext.addZuulRequestHeader(key, UrlUtils.encode(value)));
}
}
@ -88,7 +95,13 @@ public class EncodeTransferMetadataZuulEnhancedPlugin implements EnhancedPlugin
*/
private void buildMetadataHeader(RequestContext context, Map<String, String> metadata, String headerName) {
if (!CollectionUtils.isEmpty(metadata)) {
buildHeaderMap(context, ImmutableMap.of(headerName, JacksonUtils.serialize2Json(metadata)));
String encodedMetadata = JacksonUtils.serialize2Json(metadata);
try {
context.addZuulRequestHeader(headerName, URLEncoder.encode(encodedMetadata, UTF_8));
}
catch (UnsupportedEncodingException e) {
context.addZuulRequestHeader(headerName, encodedMetadata);
}
}
}

@ -0,0 +1,75 @@
/*
* 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.metadata.provider;
import java.net.URI;
import java.util.Collection;
import java.util.Map;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
import com.tencent.polaris.metadata.core.constant.MetadataConstants;
import com.tencent.polaris.metadata.core.manager.CalleeMetadataContainerGroup;
import feign.RequestTemplate;
/**
* MetadataProvider used for Feign RequestTemplate.
*
* @author Haotian Zhang
*/
public class FeignRequestTemplateMetadataProvider implements MetadataProvider {
private final RequestTemplate requestTemplate;
public FeignRequestTemplateMetadataProvider(RequestTemplate requestTemplate) {
this.requestTemplate = requestTemplate;
}
@Override
public String getRawMetadataStringValue(String key) {
switch (key) {
case MessageMetadataContainer.LABEL_KEY_METHOD:
return requestTemplate.method();
case MessageMetadataContainer.LABEL_KEY_PATH:
URI uri = URI.create(requestTemplate.request().url());
return UrlUtils.decode(uri.getPath());
case MessageMetadataContainer.LABEL_KEY_CALLER_IP:
return CalleeMetadataContainerGroup.getStaticApplicationMetadataContainer()
.getRawMetadataStringValue(MetadataConstants.LOCAL_IP);
default:
return null;
}
}
@Override
public String getRawMetadataMapValue(String key, String mapKey) {
Map<String, Collection<String>> headers = requestTemplate.headers();
switch (key) {
case MessageMetadataContainer.LABEL_MAP_KEY_HEADER:
return UrlUtils.decode(ExpressionLabelUtils.getFirstValue(headers, mapKey));
case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE:
return UrlUtils.decode(ExpressionLabelUtils.getCookieFirstValue(headers, mapKey));
case MessageMetadataContainer.LABEL_MAP_KEY_QUERY:
return UrlUtils.decode(ExpressionLabelUtils.getFirstValue(requestTemplate.queries(), mapKey));
default:
return null;
}
}
}

@ -45,7 +45,7 @@ public class ReactiveMetadataProvider implements MetadataProvider {
public String getRawMetadataStringValue(String key) {
switch (key) {
case MessageMetadataContainer.LABEL_KEY_METHOD:
return serverHttpRequest.getMethodValue();
return serverHttpRequest.getMethod().name();
case MessageMetadataContainer.LABEL_KEY_PATH:
return UrlUtils.decode(serverHttpRequest.getPath().toString());
case MessageMetadataContainer.LABEL_KEY_CALLER_IP:

@ -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.metadata.provider;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
import com.tencent.polaris.metadata.core.constant.MetadataConstants;
import com.tencent.polaris.metadata.core.manager.CalleeMetadataContainerGroup;
import org.springframework.http.HttpRequest;
/**
* MetadataProvider used for RestTemplate HttpRequest.
*
* @author Haotian Zhang
*/
public class RestTemplateMetadataProvider implements MetadataProvider {
private final HttpRequest request;
public RestTemplateMetadataProvider(HttpRequest request) {
this.request = request;
}
@Override
public String getRawMetadataStringValue(String key) {
switch (key) {
case MessageMetadataContainer.LABEL_KEY_METHOD:
return request.getMethod().toString();
case MessageMetadataContainer.LABEL_KEY_PATH:
return UrlUtils.decode(request.getURI().getPath());
case MessageMetadataContainer.LABEL_KEY_CALLER_IP:
return CalleeMetadataContainerGroup.getStaticApplicationMetadataContainer()
.getRawMetadataStringValue(MetadataConstants.LOCAL_IP);
default:
return null;
}
}
@Override
public String getRawMetadataMapValue(String key, String mapKey) {
switch (key) {
case MessageMetadataContainer.LABEL_MAP_KEY_HEADER:
return UrlUtils.decode(SpringWebExpressionLabelUtils.getHeaderValue(request, mapKey));
case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE:
return UrlUtils.decode(SpringWebExpressionLabelUtils.getCookieValue(request, mapKey));
case MessageMetadataContainer.LABEL_MAP_KEY_QUERY:
return UrlUtils.decode(SpringWebExpressionLabelUtils.getQueryValue(request, mapKey));
default:
return null;
}
}
}

@ -17,7 +17,6 @@
*/
package com.tencent.cloud.metadata.provider;
import javax.servlet.http.HttpServletRequest;
import com.tencent.cloud.common.util.UrlUtils;
@ -26,6 +25,7 @@ import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import com.tencent.polaris.metadata.core.MetadataProvider;
/**
* MetadataProvider used for Servlet.
*

@ -20,7 +20,6 @@ package com.tencent.cloud.metadata.core;
import com.tencent.cloud.common.constant.MetadataConstant;
import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.metadata.config.MetadataLocalProperties;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -55,7 +54,7 @@ public class DecodeTransferMetadataReactiveFilterTest {
@BeforeEach
public void setUp() {
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(new PolarisContextProperties());
this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter();
}
@Test

@ -1,106 +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.metadata.core;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Map;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.common.util.JacksonUtils;
import com.tencent.cloud.rpc.enhancement.zuul.EnhancedPreZuulFilter;
import org.assertj.core.api.Assertions;
import org.assertj.core.util.Maps;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockMultipartHttpServletRequest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static com.tencent.cloud.common.constant.ContextConstant.UTF_8;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_DISPOSABLE_METADATA;
import static com.tencent.cloud.common.constant.MetadataConstant.HeaderName.CUSTOM_METADATA;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
/**
* Test for {@link EncodeTransferMetadataZuulEnhancedPlugin}.
*
* @author quan, Shedfree Wu
*/
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = EncodeTransferMetadataZuulFilterTest.TestApplication.class,
properties = {"spring.config.location = classpath:application-test.yml",
"spring.main.web-application-type = reactive"})
public class EncodeTransferMetadataZuulFilterTest {
private final MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
@Autowired
private ApplicationContext applicationContext;
@BeforeEach
void setUp() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.clear();
ctx.setRequest(this.request);
}
@Test
public void testRun() throws ZuulException, UnsupportedEncodingException {
EnhancedPreZuulFilter filter = applicationContext.getBean(EnhancedPreZuulFilter.class);
RequestContext context = RequestContext.getCurrentContext();
context.set(SERVICE_ID_KEY, "test-service");
MetadataContext metadataContext = MetadataContextHolder.get();
metadataContext.setTransitiveMetadata(Maps.newHashMap("t-key", "t-value"));
metadataContext.setDisposableMetadata(Maps.newHashMap("d-key", "d-value"));
filter.run();
final RequestContext ctx = RequestContext.getCurrentContext();
Map<String, String> zuulRequestHeaders = ctx.getZuulRequestHeaders();
// convert header to lower case in com.netflix.zuul.context.RequestContext.addZuulRequestHeader
assertThat(zuulRequestHeaders.get(CUSTOM_METADATA.toLowerCase())).isNotNull();
assertThat(zuulRequestHeaders.get(CUSTOM_DISPOSABLE_METADATA.toLowerCase())).isNotNull();
String metadata = zuulRequestHeaders.get(CUSTOM_METADATA.toLowerCase());
Assertions.assertThat(metadata).isNotNull();
String decode = URLDecoder.decode(metadata, UTF_8);
Map<String, String> transitiveMap = JacksonUtils.deserialize2Map(decode);
// expect {"b":"2","t-key":"t-value"}
Assertions.assertThat(transitiveMap.size()).isEqualTo(2);
Assertions.assertThat(transitiveMap.get("b")).isEqualTo("2");
}
@SpringBootApplication
protected static class TestApplication {
}
}

@ -20,7 +20,7 @@ package com.tencent.cloud.metadata.provider;
import com.tencent.cloud.common.util.UrlUtils;
import com.tencent.polaris.metadata.core.MessageMetadataContainer;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;

@ -26,7 +26,6 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
@ -64,6 +63,10 @@
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-namespace</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>

@ -44,15 +44,20 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
public class PolarisCircuitBreaker implements CircuitBreaker, InvokeHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisCircuitBreaker.class);
private final FunctionalDecorator decorator;
private final PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf;
private final ConsumerAPI consumerAPI;
private final FunctionalDecorator decorator;
private final InvokeHandler invokeHandler;
public PolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf,
ConsumerAPI consumerAPI,
CircuitBreakAPI circuitBreakAPI) {
FunctionalDecoratorRequest makeDecoratorRequest = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod());
FunctionalDecoratorRequest makeDecoratorRequest = new FunctionalDecoratorRequest(
new ServiceKey(conf.getNamespace(), conf.getService()), conf.getProtocol(), conf.getMethod(), conf.getPath());
makeDecoratorRequest.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
makeDecoratorRequest.setResultToErrorCode(new PolarisResultToErrorCode());
this.consumerAPI = consumerAPI;

@ -17,13 +17,21 @@
package com.tencent.cloud.polaris.circuitbreaker;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerProperties;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.utils.ThreadPoolUtils;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
@ -33,7 +41,14 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
* @author seanyu 2023-02-27
*/
public class PolarisCircuitBreakerFactory
extends CircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> {
extends CircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> implements DisposableBean {
private final CircuitBreakAPI circuitBreakAPI;
private final ConsumerAPI consumerAPI;
private final ScheduledExecutorService cleanupService = Executors.newSingleThreadScheduledExecutor(
new NamedThreadFactory("sct-circuitbreaker-cleanup", true));
private Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration =
id -> {
@ -41,18 +56,22 @@ public class PolarisCircuitBreakerFactory
return new PolarisCircuitBreakerConfigBuilder()
.namespace(metadata[0])
.service(metadata[1])
.method(metadata[2])
.path(metadata[2])
.protocol(metadata[3])
.method(metadata[4])
.build();
};
private final CircuitBreakAPI circuitBreakAPI;
private final ConsumerAPI consumerAPI;
public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
public PolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI,
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties) {
this.circuitBreakAPI = circuitBreakAPI;
this.consumerAPI = consumerAPI;
cleanupService.scheduleWithFixedDelay(
() -> {
getConfigurations().clear();
},
polarisCircuitBreakerProperties.getConfigurationCleanupInterval(),
polarisCircuitBreakerProperties.getConfigurationCleanupInterval(), TimeUnit.MILLISECONDS);
}
@Override
@ -65,7 +84,7 @@ public class PolarisCircuitBreakerFactory
@Override
protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) {
String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id);
return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2]);
return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2], metadata[3], metadata[4]);
}
@Override
@ -73,4 +92,9 @@ public class PolarisCircuitBreakerFactory
this.defaultConfiguration = defaultConfiguration;
}
@Override
public void destroy() {
ThreadPoolUtils.waitAndStopThreadPools(new ExecutorService[] {cleanupService});
}
}

@ -51,7 +51,8 @@ public class ReactivePolarisCircuitBreaker implements ReactiveCircuitBreaker {
public ReactivePolarisCircuitBreaker(PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf,
ConsumerAPI consumerAPI,
CircuitBreakAPI circuitBreakAPI) {
InvokeContext.RequestContext requestContext = new FunctionalDecoratorRequest(new ServiceKey(conf.getNamespace(), conf.getService()), conf.getMethod());
InvokeContext.RequestContext requestContext = new FunctionalDecoratorRequest(
new ServiceKey(conf.getNamespace(), conf.getService()), conf.getProtocol(), conf.getMethod(), conf.getPath());
requestContext.setSourceService(new ServiceKey(conf.getSourceNamespace(), conf.getSourceService()));
requestContext.setResultToErrorCode(new PolarisResultToErrorCode());
this.consumerAPI = consumerAPI;

@ -17,13 +17,21 @@
package com.tencent.cloud.polaris.circuitbreaker;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerProperties;
import com.tencent.cloud.polaris.circuitbreaker.util.PolarisCircuitBreakerUtils;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.utils.ThreadPoolUtils;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.client.util.NamedThreadFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
@ -33,7 +41,14 @@ import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFac
* @author seanyu 2023-02-27
*/
public class ReactivePolarisCircuitBreakerFactory extends
ReactiveCircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> {
ReactiveCircuitBreakerFactory<PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration, PolarisCircuitBreakerConfigBuilder> implements DisposableBean {
private final CircuitBreakAPI circuitBreakAPI;
private final ConsumerAPI consumerAPI;
private final ScheduledExecutorService cleanupService = Executors.newSingleThreadScheduledExecutor(
new NamedThreadFactory("sct-reactive-circuitbreaker-cleanup", true));
private Function<String, PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration> defaultConfiguration =
id -> {
@ -41,17 +56,22 @@ public class ReactivePolarisCircuitBreakerFactory extends
return new PolarisCircuitBreakerConfigBuilder()
.namespace(metadata[0])
.service(metadata[1])
.method(metadata[2])
.path(metadata[2])
.protocol(metadata[3])
.method(metadata[4])
.build();
};
private final CircuitBreakAPI circuitBreakAPI;
private final ConsumerAPI consumerAPI;
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
public ReactivePolarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI,
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties) {
this.circuitBreakAPI = circuitBreakAPI;
this.consumerAPI = consumerAPI;
cleanupService.scheduleWithFixedDelay(
() -> {
getConfigurations().clear();
},
polarisCircuitBreakerProperties.getConfigurationCleanupInterval(),
polarisCircuitBreakerProperties.getConfigurationCleanupInterval(), TimeUnit.MILLISECONDS);
}
@Override
@ -64,7 +84,7 @@ public class ReactivePolarisCircuitBreakerFactory extends
@Override
protected PolarisCircuitBreakerConfigBuilder configBuilder(String id) {
String[] metadata = PolarisCircuitBreakerUtils.resolveCircuitBreakerId(id);
return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2]);
return new PolarisCircuitBreakerConfigBuilder(metadata[0], metadata[1], metadata[2], metadata[3], metadata[4]);
}
@Override
@ -73,4 +93,8 @@ public class ReactivePolarisCircuitBreakerFactory extends
this.defaultConfiguration = defaultConfiguration;
}
@Override
public void destroy() {
ThreadPoolUtils.waitAndStopThreadPools(new ExecutorService[] {cleanupService});
}
}

@ -32,15 +32,21 @@ public class PolarisCircuitBreakerConfigBuilder implements ConfigBuilder<Polaris
private String service;
private String protocol;
private String method;
private String path;
public PolarisCircuitBreakerConfigBuilder() {
}
public PolarisCircuitBreakerConfigBuilder(String namespace, String service, String method) {
public PolarisCircuitBreakerConfigBuilder(String namespace, String service, String path, String protocol, String method) {
this.namespace = namespace;
this.service = service;
this.path = path;
this.protocol = protocol;
this.method = method;
}
@ -54,17 +60,29 @@ public class PolarisCircuitBreakerConfigBuilder implements ConfigBuilder<Polaris
return this;
}
public PolarisCircuitBreakerConfigBuilder protocol(String protocol) {
this.protocol = protocol;
return this;
}
public PolarisCircuitBreakerConfigBuilder method(String method) {
this.method = method;
return this;
}
public PolarisCircuitBreakerConfigBuilder path(String path) {
this.path = path;
return this;
}
@Override
public PolarisCircuitBreakerConfiguration build() {
PolarisCircuitBreakerConfiguration conf = new PolarisCircuitBreakerConfiguration();
conf.setNamespace(namespace);
conf.setService(service);
conf.setProtocol(protocol);
conf.setMethod(method);
conf.setPath(path);
return conf;
}
@ -78,8 +96,12 @@ public class PolarisCircuitBreakerConfigBuilder implements ConfigBuilder<Polaris
private String service;
private String protocol;
private String method;
private String path;
public String getNamespace() {
return namespace;
}
@ -96,6 +118,14 @@ public class PolarisCircuitBreakerConfigBuilder implements ConfigBuilder<Polaris
this.service = service;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getMethod() {
return method;
}
@ -104,6 +134,14 @@ public class PolarisCircuitBreakerConfigBuilder implements ConfigBuilder<Polaris
this.method = method;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getSourceNamespace() {
return sourceNamespace;
}

@ -38,6 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
@ -54,6 +55,7 @@ import org.springframework.core.env.Environment;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisCircuitBreakerEnabled
@EnableConfigurationProperties(PolarisCircuitBreakerProperties.class)
@AutoConfigureAfter(RpcEnhancementAutoConfiguration.class)
public class PolarisCircuitBreakerAutoConfiguration {
@ -89,9 +91,10 @@ public class PolarisCircuitBreakerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public CircuitBreakerFactory polarisCircuitBreakerFactory(PolarisSDKContextManager polarisSDKContextManager) {
public CircuitBreakerFactory polarisCircuitBreakerFactory(PolarisSDKContextManager polarisSDKContextManager,
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties) {
PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(
polarisSDKContextManager.getCircuitBreakAPI(), polarisSDKContextManager.getConsumerAPI());
polarisSDKContextManager.getCircuitBreakAPI(), polarisSDKContextManager.getConsumerAPI(), polarisCircuitBreakerProperties);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}

@ -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.circuitbreaker.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties of Polaris CircuitBreaker .
*
*/
@ConfigurationProperties("spring.cloud.polaris.circuitbreaker")
public class PolarisCircuitBreakerProperties {
/**
* Whether enable polaris circuit-breaker function.
*/
@Value("${spring.cloud.polaris.circuitbreaker.enabled:#{true}}")
private boolean enabled = true;
/**
* Interval to clean up PolarisCircuitBreakerConfiguration, unit millisecond.
*/
@Value("${spring.cloud.polaris.circuitbreaker.configuration-cleanup-interval:#{300000}}")
private long configurationCleanupInterval = 300000;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public long getConfigurationCleanupInterval() {
return configurationCleanupInterval;
}
public void setConfigurationCleanupInterval(long configurationCleanupInterval) {
this.configurationCleanupInterval = configurationCleanupInterval;
}
}

@ -32,6 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory;
import org.springframework.context.annotation.Bean;
@ -45,6 +46,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = {"reactor.core.publisher.Mono", "reactor.core.publisher.Flux"})
@ConditionalOnPolarisCircuitBreakerEnabled
@EnableConfigurationProperties(PolarisCircuitBreakerProperties.class)
@AutoConfigureAfter(RpcEnhancementAutoConfiguration.class)
public class ReactivePolarisCircuitBreakerAutoConfiguration {
@ -67,9 +69,10 @@ public class ReactivePolarisCircuitBreakerAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class)
public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(PolarisSDKContextManager polarisSDKContextManager) {
public ReactiveCircuitBreakerFactory polarisReactiveCircuitBreakerFactory(PolarisSDKContextManager polarisSDKContextManager,
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties) {
ReactivePolarisCircuitBreakerFactory factory = new ReactivePolarisCircuitBreakerFactory(
polarisSDKContextManager.getCircuitBreakAPI(), polarisSDKContextManager.getConsumerAPI());
polarisSDKContextManager.getCircuitBreakAPI(), polarisSDKContextManager.getConsumerAPI(), polarisCircuitBreakerProperties);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}

@ -41,7 +41,7 @@ import org.springframework.boot.actuate.endpoint.annotation.Selector;
*
* @author wenxuan70
*/
@Endpoint(id = "polaris-circuit-breaker")
@Endpoint(id = "polariscircuitbreaker")
public class PolarisCircuitBreakerEndpoint {
private static final Logger LOG = LoggerFactory.getLogger(PolarisCircuitBreakerEndpoint.class);

@ -22,12 +22,15 @@ import java.net.URI;
import java.net.URISyntaxException;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.api.utils.StringUtils;
import feign.Request;
import feign.Target;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
@ -45,7 +48,7 @@ public class PolarisCircuitBreakerNameResolver {
String path = "";
// Get path in @FeignClient.
if (StringUtils.hasText(target.url())) {
if (StringUtils.isNotBlank(target.url())) {
URI uri = null;
try {
uri = new URI(target.url());
@ -58,16 +61,24 @@ public class PolarisCircuitBreakerNameResolver {
}
}
// Get path in @RequestMapping.
// Get path and method in @RequestMapping.
RequestMapping requestMapping = findMergedAnnotation(method, RequestMapping.class);
String httpMethod = Request.HttpMethod.GET.name();
if (requestMapping != null) {
path += requestMapping.path().length == 0 ?
requestMapping.value().length == 0 ? "" : requestMapping.value()[0] :
requestMapping.path()[0];
RequestMethod[] requestMethods = requestMapping.method();
if (CollectionUtils.isNotEmpty(requestMethods)) {
httpMethod = requestMethods[0].name();
}
}
return "".equals(path) ?
return StringUtils.isBlank(path) ?
MetadataContext.LOCAL_NAMESPACE + "#" + serviceName :
MetadataContext.LOCAL_NAMESPACE + "#" + serviceName + "#" + path;
MetadataContext.LOCAL_NAMESPACE + "#" + serviceName + "#" + path + "#http#" + httpMethod;
}
}

@ -76,24 +76,6 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
this.decoder = decoder;
}
/**
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// early exit if the invoked method is from java.lang.Object
@ -113,41 +95,40 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
else if ("toString".equals(method.getName())) {
return toString();
}
// disable feign.hystrix
if (circuitBreakerNameResolver == null) {
return this.dispatch.get(method).invoke(args);
}
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args);
if (circuitBreakerNameResolver != null) {
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = factory.create(circuitName);
Function<Throwable, Object> fallbackFunction;
if (this.nullableFallbackFactory != null) {
fallbackFunction = throwable -> {
Object fallback = this.nullableFallbackFactory.create(throwable);
try {
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
catch (Exception exception) {
unwrapAndRethrow(exception);
}
return null;
};
}
else {
fallbackFunction = throwable -> {
PolarisCircuitBreakerFallbackFactory.DefaultFallback fallback =
(PolarisCircuitBreakerFallbackFactory.DefaultFallback) new PolarisCircuitBreakerFallbackFactory(this.decoder).create(throwable);
return fallback.fallback(method);
};
}
try {
return circuitBreaker.run(supplier, fallbackFunction);
}
catch (FallbackWrapperException e) {
// unwrap And Rethrow
throw e.getCause();
}
Function<Throwable, Object> fallbackFunction;
if (this.nullableFallbackFactory != null) {
fallbackFunction = throwable -> {
Object fallback = this.nullableFallbackFactory.create(throwable);
try {
return this.fallbackMethodMap.get(method).invoke(fallback, args);
}
catch (Exception exception) {
unwrapAndRethrow(exception);
}
return null;
};
}
else {
return supplier.get();
fallbackFunction = throwable -> {
PolarisCircuitBreakerFallbackFactory.DefaultFallback fallback =
(PolarisCircuitBreakerFallbackFactory.DefaultFallback) new PolarisCircuitBreakerFallbackFactory(this.decoder).create(throwable);
return fallback.fallback(method);
};
}
try {
return circuitBreaker.run(supplier, fallbackFunction);
}
catch (FallbackWrapperException e) {
// unwrap And Rethrow
throw e.getCause();
}
}
@ -189,6 +170,24 @@ public class PolarisFeignCircuitBreakerInvocationHandler implements InvocationHa
};
}
/**
* If the method param of {@link InvocationHandler#invoke(Object, Method, Object[])}
* is not accessible, i.e in a package-private interface, the fallback call will cause
* of access restrictions. But methods in dispatch are copied methods. So setting
* access to dispatch method doesn't take effect to the method in
* InvocationHandler.invoke. Use map to store a copy of method to invoke the fallback
* to bypass this and reducing the count of reflection calls.
* @return cached methods map for fallback invoking
*/
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
Map<Method, Method> result = new LinkedHashMap<>();
for (Method method : dispatch.keySet()) {
method.setAccessible(true);
result.put(method, method);
}
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PolarisFeignCircuitBreakerInvocationHandler) {

@ -28,6 +28,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
import reactor.core.publisher.Flux;
@ -186,20 +187,21 @@ public class PolarisCircuitBreakerFilterFactory extends SpringCloudCircuitBreake
serviceName = route.getUri().getHost();
}
String path = exchange.getRequest().getPath().value();
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(serviceName + "#" + path);
String method = exchange.getRequest().getMethod() == null ?
"GET" : exchange.getRequest().getMethod().name();
ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(MetadataContext.LOCAL_NAMESPACE + "#" + serviceName + "#" + path + "#http#" + method);
return cb.run(
chain.filter(exchange)
.doOnSuccess(v -> {
// throw CircuitBreakerStatusCodeException by default for all need checking status
// so polaris can report right error status
Set<HttpStatus> statusNeedToCheck = new HashSet<>();
statusNeedToCheck.addAll(statuses);
statusNeedToCheck.addAll(getDefaultStatus());
HttpStatus status = exchange.getResponse().getStatusCode();
if (statusNeedToCheck.contains(status)) {
throw new CircuitBreakerStatusCodeException(status);
}
}),
chain.filter(exchange).doOnSuccess(v -> {
// throw CircuitBreakerStatusCodeException by default for all need checking status
// so polaris can report right error status
Set<HttpStatus> statusNeedToCheck = new HashSet<>();
statusNeedToCheck.addAll(statuses);
statusNeedToCheck.addAll(getDefaultStatus());
HttpStatus status = exchange.getResponse().getStatusCode();
if (statusNeedToCheck.contains(status)) {
throw new CircuitBreakerStatusCodeException(status);
}
}),
t -> {
// pre-check CircuitBreakerStatusCodeException's status matches input status
if (t instanceof CircuitBreakerStatusCodeException) {

@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
import java.io.IOException;
import java.lang.reflect.Method;
import com.tencent.cloud.common.metadata.MetadataContext;
import com.tencent.cloud.polaris.circuitbreaker.exception.FallbackWrapperException;
import com.tencent.polaris.api.pojo.CircuitBreakerStatus;
import com.tencent.polaris.circuitbreak.client.exception.CallAbortedException;
@ -59,13 +60,18 @@ public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpR
this.polarisCircuitBreaker = polarisCircuitBreaker;
this.applicationContext = applicationContext;
this.circuitBreakerFactory = circuitBreakerFactory;
this.restTemplate = restTemplate;
this.restTemplate = restTemplate;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
try {
return circuitBreakerFactory.create(request.getURI().getHost() + "#" + request.getURI().getPath()).run(
String httpMethod = "GET";
if (request.getMethod() != null) {
httpMethod = request.getMethod().name();
}
return circuitBreakerFactory.create(MetadataContext.LOCAL_NAMESPACE + "#" + request.getURI()
.getHost() + "#" + request.getURI().getPath() + "#http#" + httpMethod).run(
() -> {
try {
ClientHttpResponse response = execution.execute(request, body);
@ -84,7 +90,8 @@ public class PolarisCircuitBreakerRestTemplateInterceptor implements ClientHttpR
CircuitBreakerStatus.FallbackInfo fallbackInfo = new CircuitBreakerStatus.FallbackInfo(200, null, polarisCircuitBreaker.fallback());
return new PolarisCircuitBreakerHttpResponse(fallbackInfo);
}
if (!PolarisCircuitBreakerFallback.class.toGenericString().equals(polarisCircuitBreaker.fallbackClass().toGenericString())) {
if (!PolarisCircuitBreakerFallback.class.toGenericString()
.equals(polarisCircuitBreaker.fallbackClass().toGenericString())) {
Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerFallback.class, "fallback");
PolarisCircuitBreakerFallback polarisCircuitBreakerFallback = applicationContext.getBean(polarisCircuitBreaker.fallbackClass());
return (PolarisCircuitBreakerHttpResponse) ReflectionUtils.invokeMethod(method, polarisCircuitBreakerFallback);

@ -45,29 +45,36 @@ public final class PolarisCircuitBreakerUtils {
}
/**
* Format:
* 0. namespace#service#path#protocol#method
* 1. namespace#service#method
* 2. service#method
* 3. service
* namespace set as default spring.cloud.polaris.namespace if absent.
*
* @param id CircuitBreakerId
* Format: namespace#service#method or service#method or service ,
* namespace set as default spring.cloud.polaris.namespace if absent
* @return String[]{namespace, service, method}
*/
public static String[] resolveCircuitBreakerId(String id) {
Assert.hasText(id, "A CircuitBreaker must have an id. Id could be : namespace#service#method or service#method or service");
String[] polarisCircuitBreakerMetaData = id.split("#");
if (polarisCircuitBreakerMetaData.length == 2) {
return new String[]{MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1]};
return new String[] {MetadataContext.LOCAL_NAMESPACE, polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], "http", ""};
}
if (polarisCircuitBreakerMetaData.length == 3) {
return new String[]{polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2]};
return new String[] {polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2], "http", ""};
}
return new String[]{MetadataContext.LOCAL_NAMESPACE, id, ""};
if (polarisCircuitBreakerMetaData.length == 5) {
return new String[] {polarisCircuitBreakerMetaData[0], polarisCircuitBreakerMetaData[1], polarisCircuitBreakerMetaData[2], polarisCircuitBreakerMetaData[3], polarisCircuitBreakerMetaData[4]};
}
return new String[] {MetadataContext.LOCAL_NAMESPACE, id, "", "http", ""};
}
public static void reportStatus(ConsumerAPI consumerAPI,
PolarisCircuitBreakerConfigBuilder.PolarisCircuitBreakerConfiguration conf, CallAbortedException e) {
try {
ServiceCallResult result = new ServiceCallResult();
result.setMethod(conf.getMethod());
result.setMethod(conf.getPath());
result.setNamespace(conf.getNamespace());
result.setService(conf.getService());
result.setRuleName(e.getRuleName());

@ -97,11 +97,12 @@ public class PolarisCircuitBreakerZuulFilter extends ZuulFilter {
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
String serviceId = ZuulFilterUtils.getServiceId(context);
String path = ZuulFilterUtils.getPath(context);
String circuitName = "".equals(path) ?
String circuitName = com.tencent.polaris.api.utils.StringUtils.isBlank(path) ?
MetadataContext.LOCAL_NAMESPACE + "#" + serviceId :
MetadataContext.LOCAL_NAMESPACE + "#" + serviceId + "#" + path;
MetadataContext.LOCAL_NAMESPACE + "#" + serviceId + "#" + path + "#http#" + context.getRequest().getMethod();
CircuitBreaker circuitBreaker = circuitBreakerFactory.create(circuitName);
if (circuitBreaker instanceof PolarisCircuitBreaker) {
PolarisCircuitBreaker polarisCircuitBreaker = (PolarisCircuitBreaker) circuitBreaker;

@ -36,7 +36,8 @@ public class PolarisFeignCircuitBreakerTargeter implements Targeter {
private final PolarisCircuitBreakerNameResolver circuitBreakerNameResolver;
public PolarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) {
public PolarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory,
PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerFactory = circuitBreakerFactory;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
}

@ -3,7 +3,14 @@
{
"name": "spring.cloud.polaris.circuitbreaker.enabled",
"type": "java.lang.Boolean",
"defaultValue": "true"
"defaultValue": "true",
"description": "If polaris circuitbreaker enabled."
},
{
"name": "spring.cloud.polaris.circuitbreaker.configuration-cleanup-interval",
"type": "java.lang.Long",
"defaultValue": "300000",
"description": "Interval to clean up PolarisCircuitBreakerConfiguration, unit millisecond."
}
],
"hints": []

@ -30,12 +30,14 @@ import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerProperties;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.config.Configuration;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.api.SDKContext;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
@ -70,6 +72,8 @@ public class PolarisCircuitBreakerMockServerTest {
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
private static NamingServer namingServer;
private static SDKContext context;
@BeforeAll
public static void beforeAll() throws IOException {
mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class);
@ -78,11 +82,10 @@ public class PolarisCircuitBreakerMockServerTest {
mockedApplicationContextAwareUtils.when(() -> ApplicationContextAwareUtils.getProperties("spring.cloud.polaris.service"))
.thenReturn(SERVICE_CIRCUIT_BREAKER);
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(-1);
System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort()));
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, SERVICE_CIRCUIT_BREAKER);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerMockServerTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
@ -93,6 +96,9 @@ public class PolarisCircuitBreakerMockServerTest {
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
Configuration configuration = TestUtils.configWithEnvAddress();
context = SDKContext.initContextByConfig(configuration);
}
@AfterAll
@ -103,15 +109,17 @@ public class PolarisCircuitBreakerMockServerTest {
if (null != mockedApplicationContextAwareUtils) {
mockedApplicationContextAwareUtils.close();
}
if (null != context) {
context.close();
}
}
@Test
public void testCircuitBreaker() {
Configuration configuration = TestUtils.configWithEnvAddress();
CircuitBreakAPI circuitBreakAPI = CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration);
ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByConfig(configuration);
PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
CircuitBreakAPI circuitBreakAPI = CircuitBreakAPIFactory.createCircuitBreakAPIByContext(context);
ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPIByContext(context);
PolarisCircuitBreakerProperties polarisCircuitBreakerProperties = new PolarisCircuitBreakerProperties();
PolarisCircuitBreakerFactory polarisCircuitBreakerFactory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI, polarisCircuitBreakerProperties);
CircuitBreaker cb = polarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER);
// trigger fallback for 5 times
@ -132,7 +140,7 @@ public class PolarisCircuitBreakerMockServerTest {
assertThat(resList).isEqualTo(Arrays.asList("invoke success", "fallback", "fallback", "fallback", "fallback"));
// always fallback
ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
ReactivePolarisCircuitBreakerFactory reactivePolarisCircuitBreakerFactory = new ReactivePolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI, polarisCircuitBreakerProperties);
ReactiveCircuitBreaker rcb = reactivePolarisCircuitBreakerFactory.create(SERVICE_CIRCUIT_BREAKER);
assertThat(Mono.just("foobar").transform(it -> rcb.run(it, t -> Mono.just("fallback")))

@ -18,13 +18,19 @@
package com.tencent.cloud.polaris.circuitbreaker;
import java.lang.reflect.Method;
import java.util.Map;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerAutoConfiguration;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.polaris.client.util.Utils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -56,7 +62,8 @@ public class PolarisCircuitBreakerTest {
LoadBalancerAutoConfiguration.class,
PolarisCircuitBreakerFeignClientAutoConfiguration.class,
PolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true")
.withPropertyValues("spring.cloud.polaris.circuitbreaker.configuration-cleanup-interval=5000");
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@ -92,6 +99,18 @@ public class PolarisCircuitBreakerTest {
throw new RuntimeException("boom");
}, t -> "fallback")).isEqualTo("fallback");
Method getConfigurationsMethod = ReflectionUtils.findMethod(PolarisCircuitBreakerFactory.class,
"getConfigurations");
Assertions.assertNotNull(getConfigurationsMethod);
ReflectionUtils.makeAccessible(getConfigurationsMethod);
Map<?, ?> values = (Map<?, ?>) ReflectionUtils.invokeMethod(getConfigurationsMethod, polarisCircuitBreakerFactory);
Assertions.assertNotNull(values);
Assertions.assertEquals(1, values.size());
Utils.sleepUninterrupted(10 * 1000);
Assertions.assertEquals(0, values.size());
});
}

@ -17,15 +17,20 @@
package com.tencent.cloud.polaris.circuitbreaker;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import com.tencent.cloud.common.util.ApplicationContextAwareUtils;
import com.tencent.cloud.common.util.ReflectionUtils;
import com.tencent.cloud.polaris.circuitbreaker.common.PolarisCircuitBreakerConfigBuilder;
import com.tencent.cloud.polaris.circuitbreaker.config.ReactivePolarisCircuitBreakerAutoConfiguration;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import com.tencent.polaris.client.util.Utils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -58,7 +63,8 @@ public class ReactivePolarisCircuitBreakerTest {
RpcEnhancementAutoConfiguration.class,
LoadBalancerAutoConfiguration.class,
ReactivePolarisCircuitBreakerAutoConfiguration.class))
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true")
.withPropertyValues("spring.cloud.polaris.circuitbreaker.configuration-cleanup-interval=5000");
private static MockedStatic<ApplicationContextAwareUtils> mockedApplicationContextAwareUtils;
@ -97,6 +103,18 @@ public class ReactivePolarisCircuitBreakerTest {
assertThat(Flux.error(new RuntimeException("boom")).transform(it -> cb.run(it, t -> Flux.just("fallback")))
.collectList().block()).isEqualTo(Collections.singletonList("fallback"));
Method getConfigurationsMethod = ReflectionUtils.findMethod(PolarisCircuitBreakerFactory.class,
"getConfigurations");
Assertions.assertNotNull(getConfigurationsMethod);
ReflectionUtils.makeAccessible(getConfigurationsMethod);
Map<?, ?> values = (Map<?, ?>) ReflectionUtils.invokeMethod(getConfigurationsMethod, polarisCircuitBreakerFactory);
Assertions.assertNotNull(values);
Assertions.assertTrue(values.size() >= 0);
Utils.sleepUninterrupted(10 * 1000);
// clear by cleanupService in ReactivePolarisCircuitBreakerFactory
Assertions.assertEquals(0, values.size());
});
}

@ -17,8 +17,10 @@
package com.tencent.cloud.polaris.circuitbreaker.config;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.polaris.context.config.PolarisContextAutoConfiguration;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementAutoConfiguration;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -42,6 +44,11 @@ public class PolarisCircuitBreakerBootstrapConfigurationTest {
.withPropertyValues("spring.cloud.polaris.enabled=true")
.withPropertyValues("spring.cloud.polaris.circuitbreaker.enabled=true");
@BeforeAll
static void beforeAll() {
PolarisSDKContextManager.innerDestroy();
}
@Test
public void testDefaultInitialization() {
this.contextRunner.run(context -> {

@ -36,7 +36,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
@SpringBootTest(webEnvironment = RANDOM_PORT,
classes = PolarisCircuitBreakerFeignIntegrationTest.TestConfig.class,
properties = {
"feign.hystrix.enabled=false",
"feign.hystrix.enabled=false",
"spring.cloud.gateway.enabled=false",
"feign.circuitbreaker.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,

@ -19,47 +19,32 @@ package com.tencent.cloud.polaris.circuitbreaker.feign;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.common.TestUtils;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.PolarisFeignCircuitBreakerTargeter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@ -68,7 +53,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@ -82,6 +66,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
properties = {
"feign.hystrix.enabled=true",
"spring.cloud.gateway.enabled=false",
"spring.cloud.polaris.address=grpc://127.0.0.1:10081",
"feign.circuitbreaker.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=test"
@ -90,6 +75,8 @@ public class PolarisCircuitBreakerFeignIntegrationTest {
private static final String TEST_SERVICE_NAME = "test-service-callee";
private static NamingServer namingServer;
@Autowired
private EchoService echoService;
@ -102,6 +89,31 @@ public class PolarisCircuitBreakerFeignIntegrationTest {
@Autowired
private BazService bazService;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(10081);
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerFeignIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@Test
public void contextLoads() {
assertThat(echoService).isNotNull();
@ -170,9 +182,6 @@ public class PolarisCircuitBreakerFeignIntegrationTest {
@EnableFeignClients
public static class TestConfig {
@Autowired(required = false)
private List<Customizer<PolarisCircuitBreakerFactory>> customizers = new ArrayList<>();
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
@ -182,74 +191,6 @@ public class PolarisCircuitBreakerFeignIntegrationTest {
public CustomFallbackFactory customFallbackFactory() {
return new CustomFallbackFactory();
}
@Bean
public PreDestroy preDestroy(NamingServer namingServer) {
return new PreDestroy(namingServer);
}
@Bean
public NamingServer namingServer() throws IOException {
NamingServer namingServer = NamingServer.startNamingServer(-1);
System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort()));
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerFeignIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
return namingServer;
}
@Bean
public CircuitBreakAPI circuitBreakAPI(NamingServer namingServer) {
com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress();
return CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration);
}
@Bean
public ConsumerAPI consumerAPI(NamingServer namingServer) {
com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress();
return DiscoveryAPIFactory.createConsumerAPIByConfig(configuration);
}
@Bean
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
CircuitBreakAPI circuitBreakAPI) {
return new SuccessCircuitBreakerReporter(properties, circuitBreakAPI);
}
@Bean
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
CircuitBreakAPI circuitBreakAPI) {
return new ExceptionCircuitBreakerReporter(properties, circuitBreakAPI);
}
@Bean
public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}
@Bean
public PolarisCircuitBreakerNameResolver polarisCircuitBreakerNameResolver() {
return new PolarisCircuitBreakerNameResolver();
}
@Bean
@Primary
@ConditionalOnBean(CircuitBreakerFactory.class)
@ConditionalOnProperty(value = "feign.hystrix.enabled", havingValue = "true")
public PolarisFeignCircuitBreakerTargeter polarisFeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, PolarisCircuitBreakerNameResolver circuitBreakerNameResolver) {
return new PolarisFeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerNameResolver);
}
}
public static class EchoServiceFallback implements EchoService {
@ -284,19 +225,4 @@ public class PolarisCircuitBreakerFeignIntegrationTest {
}
}
public static class PreDestroy implements DisposableBean {
private final NamingServer namingServer;
public PreDestroy(NamingServer namingServer) {
this.namingServer = namingServer;
}
@Override
public void destroy() throws Exception {
namingServer.terminate();
}
}
}

@ -93,7 +93,7 @@ public class PolarisCircuitBreakerNameResolverTest {
Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerNameResolverTest.class, "mockRequestMapping2");
PolarisCircuitBreakerNameResolver resolver = new PolarisCircuitBreakerNameResolver();
String polarisCircuitBreakerName = resolver.resolveCircuitBreakerName("test", target, method);
assertThat(polarisCircuitBreakerName).isEqualTo("Test#test-svc#/");
assertThat(polarisCircuitBreakerName).isEqualTo("Test#test-svc#/#http#GET");
}
@Test
@ -103,7 +103,7 @@ public class PolarisCircuitBreakerNameResolverTest {
Method method = ReflectionUtils.findMethod(PolarisCircuitBreakerNameResolverTest.class, "mockRequestMapping3");
PolarisCircuitBreakerNameResolver resolver = new PolarisCircuitBreakerNameResolver();
String polarisCircuitBreakerName = resolver.resolveCircuitBreakerName("test", target, method);
assertThat(polarisCircuitBreakerName).isEqualTo("Test#test-svc#/");
assertThat(polarisCircuitBreakerName).isEqualTo("Test#test-svc#/#http#GET");
}

@ -19,43 +19,30 @@ package com.tencent.cloud.polaris.circuitbreaker.gateway;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.core.ConsumerAPI;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.factory.api.DiscoveryAPIFactory;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.common.TestUtils;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
@ -69,8 +56,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.Consts.SERVICE_CIRCUIT_BREAKER;
import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV;
import static org.assertj.core.api.Assertions.assertThat;
@ -83,7 +68,7 @@ import static org.assertj.core.api.Assertions.assertThat;
properties = {
"spring.cloud.gateway.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=" + SERVICE_CIRCUIT_BREAKER,
"spring.cloud.polaris.service=test",
"spring.main.web-application-type=reactive"
},
classes = PolarisCircuitBreakerGatewayIntegrationTest.TestApplication.class
@ -94,12 +79,39 @@ public class PolarisCircuitBreakerGatewayIntegrationTest {
private static final String TEST_SERVICE_NAME = "test-service-callee";
private static NamingServer namingServer;
@Autowired
private WebTestClient webClient;
@Autowired
private ApplicationContext applicationContext;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(10081);
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerGatewayIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@Test
public void fallback() throws Exception {
SpringCloudCircuitBreakerFilterFactory.Config config = new SpringCloudCircuitBreakerFilterFactory.Config();
@ -161,67 +173,6 @@ public class PolarisCircuitBreakerGatewayIntegrationTest {
@EnableAutoConfiguration
public static class TestApplication {
@Autowired(required = false)
private List<Customizer<PolarisCircuitBreakerFactory>> customizers = new ArrayList<>();
@Bean
public PreDestroy preDestroy(NamingServer namingServer) {
return new PreDestroy(namingServer);
}
@Bean
public NamingServer namingServer() throws IOException {
NamingServer namingServer = NamingServer.startNamingServer(-1);
System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort()));
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerGatewayIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
return namingServer;
}
@Bean
public CircuitBreakAPI circuitBreakAPI(NamingServer namingServer) throws IOException {
com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress();
return CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration);
}
@Bean
public ConsumerAPI consumerAPI(NamingServer namingServer) {
com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress();
return DiscoveryAPIFactory.createConsumerAPIByConfig(configuration);
}
@Bean
@ConditionalOnMissingBean(SuccessCircuitBreakerReporter.class)
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
CircuitBreakAPI circuitBreakAPI) {
return new SuccessCircuitBreakerReporter(properties, circuitBreakAPI);
}
@Bean
@ConditionalOnMissingBean(ExceptionCircuitBreakerReporter.class)
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
CircuitBreakAPI circuitBreakAPI) {
return new ExceptionCircuitBreakerReporter(properties, circuitBreakAPI);
}
@Bean
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI, ConsumerAPI consumerAPI) {
PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(circuitBreakAPI, consumerAPI);
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
Set<String> codeSets = new HashSet<>();
@ -264,19 +215,4 @@ public class PolarisCircuitBreakerGatewayIntegrationTest {
}
}
public static class PreDestroy implements DisposableBean {
private final NamingServer namingServer;
public PreDestroy(NamingServer namingServer) {
this.namingServer = namingServer;
}
@Override
public void destroy() throws Exception {
namingServer.terminate();
}
}
}

@ -18,42 +18,31 @@
package com.tencent.cloud.polaris.circuitbreaker.resttemplate;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import com.google.protobuf.util.JsonFormat;
import com.tencent.cloud.polaris.circuitbreaker.PolarisCircuitBreakerFactory;
import com.tencent.cloud.polaris.circuitbreaker.config.PolarisCircuitBreakerFeignClientAutoConfiguration;
import com.tencent.cloud.polaris.circuitbreaker.reporter.ExceptionCircuitBreakerReporter;
import com.tencent.cloud.polaris.circuitbreaker.reporter.SuccessCircuitBreakerReporter;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties;
import com.tencent.polaris.api.pojo.ServiceKey;
import com.tencent.polaris.circuitbreak.api.CircuitBreakAPI;
import com.tencent.polaris.circuitbreak.factory.CircuitBreakAPIFactory;
import com.tencent.polaris.client.util.Utils;
import com.tencent.polaris.specification.api.v1.fault.tolerance.CircuitBreakerProto;
import com.tencent.polaris.test.common.TestUtils;
import com.tencent.polaris.test.mock.discovery.NamingServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.ApplicationContext;
@ -72,7 +61,6 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;
import static com.tencent.polaris.test.common.Consts.NAMESPACE_TEST;
import static com.tencent.polaris.test.common.TestUtils.SERVER_ADDRESS_ENV;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@ -88,6 +76,7 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
classes = PolarisCircuitBreakerRestTemplateIntegrationTest.TestConfig.class,
properties = {
"spring.cloud.gateway.enabled=false",
"spring.cloud.polaris.address=grpc://127.0.0.1:10081",
"feign.circuitbreaker.enabled=true",
"spring.cloud.polaris.namespace=" + NAMESPACE_TEST,
"spring.cloud.polaris.service=test"
@ -96,6 +85,8 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
private static final String TEST_SERVICE_NAME = "test-service-callee";
private static NamingServer namingServer;
@Autowired
@Qualifier("defaultRestTemplate")
private RestTemplate defaultRestTemplate;
@ -123,6 +114,29 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
@Autowired
private ApplicationContext applicationContext;
@BeforeAll
static void beforeAll() throws Exception {
PolarisSDKContextManager.innerDestroy();
namingServer = NamingServer.startNamingServer(10081);
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerRestTemplateIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
}
@AfterAll
static void afterAll() {
if (null != namingServer) {
namingServer.terminate();
}
}
@Test
public void testRestTemplate() throws URISyntaxException {
@ -149,7 +163,8 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode2.getForObject("/example/service/b/info", String.class)).isEqualTo("\"this is a fallback class\"");
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode3.getForEntity("/example/service/b/info", String.class).getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(restTemplateFallbackFromCode3.getForEntity("/example/service/b/info", String.class)
.getStatusCode()).isEqualTo(HttpStatus.OK);
Utils.sleepUninterrupted(2000);
assertThat(restTemplateFallbackFromCode4.getForObject("/example/service/b/info", String.class)).isEqualTo("fallback");
Utils.sleepUninterrupted(2000);
@ -166,13 +181,6 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
@EnableFeignClients
public static class TestConfig {
@Autowired(required = false)
private List<Customizer<PolarisCircuitBreakerFactory>> customizers = new ArrayList<>();
{
PolarisSDKContextManager.innerDestroy();
}
@Bean
@com.tencent.cloud.polaris.circuitbreaker.resttemplate.PolarisCircuitBreaker(fallback = "fallback")
public RestTemplate defaultRestTemplate() {
@ -244,56 +252,6 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
return new CustomPolarisCircuitBreakerFallback3();
}
@Bean
public NamingServer namingServer() throws IOException {
NamingServer namingServer = NamingServer.startNamingServer(-1);
System.setProperty(SERVER_ADDRESS_ENV, String.format("127.0.0.1:%d", namingServer.getPort()));
ServiceKey serviceKey = new ServiceKey(NAMESPACE_TEST, TEST_SERVICE_NAME);
CircuitBreakerProto.CircuitBreakerRule.Builder circuitBreakerRuleBuilder = CircuitBreakerProto.CircuitBreakerRule.newBuilder();
InputStream inputStream = PolarisCircuitBreakerRestTemplateIntegrationTest.class.getClassLoader()
.getResourceAsStream("circuitBreakerRule.json");
String json = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines()
.collect(Collectors.joining(""));
JsonFormat.parser().ignoringUnknownFields().merge(json, circuitBreakerRuleBuilder);
CircuitBreakerProto.CircuitBreakerRule circuitBreakerRule = circuitBreakerRuleBuilder.build();
CircuitBreakerProto.CircuitBreaker circuitBreaker = CircuitBreakerProto.CircuitBreaker.newBuilder()
.addRules(circuitBreakerRule).build();
namingServer.getNamingService().setCircuitBreaker(serviceKey, circuitBreaker);
return namingServer;
}
@Bean
public PreDestroy preDestroy(NamingServer namingServer) {
return new PreDestroy(namingServer);
}
@Bean
public CircuitBreakAPI circuitBreakAPI(NamingServer namingServer) {
com.tencent.polaris.api.config.Configuration configuration = TestUtils.configWithEnvAddress();
return CircuitBreakAPIFactory.createCircuitBreakAPIByConfig(configuration);
}
@Bean
public SuccessCircuitBreakerReporter successCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
CircuitBreakAPI circuitBreakAPI) {
return new SuccessCircuitBreakerReporter(properties, circuitBreakAPI);
}
@Bean
public ExceptionCircuitBreakerReporter exceptionCircuitBreakerReporter(RpcEnhancementReporterProperties properties,
CircuitBreakAPI circuitBreakAPI) {
return new ExceptionCircuitBreakerReporter(properties, circuitBreakAPI);
}
@Bean
public CircuitBreakerFactory polarisCircuitBreakerFactory(CircuitBreakAPI circuitBreakAPI,
PolarisSDKContextManager polarisSDKContextManager) {
PolarisCircuitBreakerFactory factory = new PolarisCircuitBreakerFactory(
circuitBreakAPI, polarisSDKContextManager.getConsumerAPI());
customizers.forEach(customizer -> customizer.customize(factory));
return factory;
}
@RestController
@RequestMapping("/example/service/b")
public class ServiceBController {
@ -342,19 +300,4 @@ public class PolarisCircuitBreakerRestTemplateIntegrationTest {
);
}
}
public static class PreDestroy implements DisposableBean {
private final NamingServer namingServer;
public PreDestroy(NamingServer namingServer) {
this.namingServer = namingServer;
}
@Override
public void destroy() throws Exception {
namingServer.terminate();
}
}
}

@ -71,9 +71,11 @@ public class PolarisCircuitBreakerUtilsTests {
@Test
public void testResolveCircuitBreakerId() {
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_svc")).isEqualTo(new String[]{NAMESPACE_TEST, "test_svc", ""});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_svc#test_path")).isEqualTo(new String[]{NAMESPACE_TEST, "test_svc", "test_path"});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_ns#test_svc#test_path")).isEqualTo(new String[]{"test_ns", "test_svc", "test_path"});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_svc")).isEqualTo(new String[] {NAMESPACE_TEST, "test_svc", "", "http", ""});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_svc#test_path")).isEqualTo(new String[] {NAMESPACE_TEST, "test_svc", "test_path", "http", ""});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_ns#test_svc#test_path")).isEqualTo(new String[] {"test_ns", "test_svc", "test_path", "http", ""});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_ns#test_svc#test_path")).isEqualTo(new String[] {"test_ns", "test_svc", "test_path", "http", ""});
assertThat(PolarisCircuitBreakerUtils.resolveCircuitBreakerId("test_ns#test_svc#test_path#tcp#POST")).isEqualTo(new String[] {"test_ns", "test_svc", "test_path", "tcp", "POST"});
}
}

@ -1,22 +1,15 @@
spring:
main:
web-application-type: reactive
application:
name: GatewayScgService
cloud:
tencent:
plugin:
scg:
staining:
enabled: true
rule-staining:
enabled: true
router:
feature-env:
enabled: true
polaris:
address: grpc://127.0.0.1:10081
namespace: default
namespace: Test
enabled: true
gateway:
enabled: true
routes:
- id: cb-test
uri: http://localhost:${server.port}/hello/1

@ -34,6 +34,10 @@
<groupId>com.tencent.polaris</groupId>
<artifactId>router-nearby</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-namespace</artifactId>
</exclusion>
<exclusion>
<groupId>com.tencent.polaris</groupId>
<artifactId>router-metadata</artifactId>

@ -13,7 +13,6 @@
* 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;
@ -26,27 +25,27 @@ import com.tencent.cloud.common.constant.OrderConstant;
import com.tencent.cloud.common.util.AddressUtils;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.config.PolarisCryptoConfigProperties;
import com.tencent.cloud.polaris.context.PolarisConfigModifier;
import com.tencent.cloud.polaris.context.PolarisConfigurationConfigModifier;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.api.utils.CollectionUtils;
import com.tencent.polaris.factory.config.ConfigurationImpl;
import com.tencent.polaris.factory.config.configuration.ConfigFilterConfigImpl;
import com.tencent.polaris.factory.config.configuration.ConnectorConfigImpl;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import static com.tencent.polaris.api.config.plugin.DefaultPlugins.LOCAL_FILE_CONNECTOR_TYPE;
/**
* Read configuration from spring cloud's configuration file and override polaris.yaml.
*
* @author lepdou 2022-03-10
*/
public class ConfigurationModifier implements PolarisConfigModifier {
public class ConfigurationModifier implements PolarisConfigurationConfigModifier {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationModifier.class);
private static final String DATA_SOURCE_POLARIS = "polaris";
private static final String DATA_SOURCE_LOCAL = "local";
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisCryptoConfigProperties polarisCryptoConfigProperties;
@ -63,16 +62,15 @@ public class ConfigurationModifier implements PolarisConfigModifier {
@Override
public void modify(ConfigurationImpl configuration) {
if (StringUtils.equalsIgnoreCase(polarisConfigProperties.getDataSource(), DATA_SOURCE_POLARIS)) {
initByPolarisDataSource(configuration);
}
else if (StringUtils.equalsIgnoreCase(polarisConfigProperties.getDataSource(), DATA_SOURCE_LOCAL)) {
initByLocalDataSource(configuration);
}
else {
throw new RuntimeException("Unsupported config data source");
configuration.getGlobal().getAPI().setReportEnable(false);
configuration.getGlobal().getStatReporter().setEnable(false);
if (!polarisContextProperties.getEnabled() || !polarisConfigProperties.isEnabled()) {
return;
}
initDataSource(configuration);
ConfigFilterConfigImpl configFilterConfig = configuration.getConfigFile().getConfigFilterConfig();
configFilterConfig.setEnable(polarisCryptoConfigProperties.isEnabled());
if (polarisCryptoConfigProperties.isEnabled()) {
@ -81,18 +79,15 @@ public class ConfigurationModifier implements PolarisConfigModifier {
}
}
private void initByLocalDataSource(ConfigurationImpl configuration) {
configuration.getConfigFile().getServerConnector().setConnectorType("localFile");
String localFileRootPath = polarisConfigProperties.getLocalFileRootPath();
configuration.getConfigFile().getServerConnector().setPersistDir(localFileRootPath);
LOGGER.info("[SCT] Run spring cloud tencent config with local data source. localFileRootPath = {}", localFileRootPath);
}
private void initByPolarisDataSource(ConfigurationImpl configuration) {
private void initDataSource(ConfigurationImpl configuration) {
// set connector type
configuration.getConfigFile().getServerConnector().setConnectorType("polaris");
configuration.getConfigFile().getServerConnector().setConnectorType(polarisConfigProperties.getDataSource());
if (StringUtils.equalsIgnoreCase(polarisConfigProperties.getDataSource(), LOCAL_FILE_CONNECTOR_TYPE)) {
String localFileRootPath = polarisConfigProperties.getLocalFileRootPath();
configuration.getConfigFile().getServerConnector().setPersistDir(localFileRootPath);
LOGGER.info("[SCT] Run spring cloud tencent config with local data source. localFileRootPath = {}", localFileRootPath);
return;
}
// set config server address
List<String> configAddresses;
@ -114,6 +109,11 @@ public class ConfigurationModifier implements PolarisConfigModifier {
configuration.getConfigFile().getServerConnector().setAddresses(configAddresses);
if (StringUtils.isNotEmpty(polarisConfigProperties.getToken())) {
ConnectorConfigImpl connectorConfig = configuration.getConfigFile().getServerConnector();
connectorConfig.setToken(polarisConfigProperties.getToken());
}
LOGGER.info("[SCT] Run spring cloud tencent config in polaris data source.");
}

@ -21,7 +21,6 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.AffectedConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigPropertyRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
import com.tencent.cloud.polaris.config.annotation.PolarisConfigAnnotationProcessor;
@ -68,7 +67,6 @@ public class PolarisConfigAutoConfiguration {
return new PolarisConfigLoggerApplicationListener();
}
@Bean
@Primary
@ConditionalOnReflectRefreshType
@ -79,9 +77,9 @@ public class PolarisConfigAutoConfiguration {
@Bean
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public PolarisConfigPropertyRefresher polarisRefreshContextPropertySourceAutoRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager, ContextRefresher contextRefresher) {
return new PolarisRefreshEntireContextRefresher(polarisConfigProperties, polarisPropertySourceManager, contextRefresher);
public PolarisConfigPropertyRefresher polarisRefreshContextPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties, ContextRefresher contextRefresher) {
return new PolarisRefreshEntireContextRefresher(polarisConfigProperties, contextRefresher);
}
@Configuration(proxyBeanMethods = false)
@ -105,10 +103,10 @@ public class PolarisConfigAutoConfiguration {
}
@Bean
public PolarisConfigPropertyRefresher polarisReflectPropertySourceAutoRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager, SpringValueRegistry springValueRegistry,
public PolarisConfigPropertyRefresher polarisReflectPropertySourceAutoRefresher(
PolarisConfigProperties polarisConfigProperties, SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) {
return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, polarisPropertySourceManager,
return new PolarisRefreshAffectedContextRefresher(polarisConfigProperties,
springValueRegistry, placeholderHelper);
}

@ -20,7 +20,6 @@ package com.tencent.cloud.polaris.config;
import com.tencent.cloud.polaris.config.adapter.AffectedConfigurationPropertiesRebinder;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigFileLocator;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.condition.ConditionalOnReflectRefreshType;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.config.PolarisCryptoConfigProperties;
@ -37,6 +36,7 @@ import org.springframework.cloud.context.properties.ConfigurationPropertiesRebin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
/**
@ -59,16 +59,10 @@ public class PolarisConfigBootstrapAutoConfiguration {
return new PolarisCryptoConfigProperties();
}
@Bean
@ConditionalOnMissingBean
public PolarisPropertySourceManager polarisPropertySourceManager() {
return new PolarisPropertySourceManager();
}
@Bean
@ConditionalOnConnectRemoteServerEnabled
public ConfigFileService configFileService(PolarisSDKContextManager polarisSDKContextManager) {
return ConfigFileServiceFactory.createConfigFileService(polarisSDKContextManager.getSDKContext());
return ConfigFileServiceFactory.createConfigFileService(polarisSDKContextManager.getConfigSDKContext());
}
@Bean
@ -77,11 +71,9 @@ public class PolarisConfigBootstrapAutoConfiguration {
PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties,
ConfigFileService configFileService,
PolarisPropertySourceManager polarisPropertySourceManager,
Environment environment) {
return new PolarisConfigFileLocator(polarisConfigProperties,
polarisContextProperties, configFileService,
polarisPropertySourceManager, environment);
polarisContextProperties, configFileService, environment);
}
@Bean
@ -93,6 +85,7 @@ public class PolarisConfigBootstrapAutoConfiguration {
}
@Bean
@Primary
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
@ConditionalOnReflectRefreshType
public ConfigurationPropertiesRebinder affectedConfigurationPropertiesRebinder(

@ -43,13 +43,16 @@ import org.springframework.util.StringUtils;
/**
* Optimize {@link ConfigurationPropertiesRebinder}, only rebuild affected beans.
* @author weihubeats 2022-7-10
*
* @author weihubeats
*/
public class AffectedConfigurationPropertiesRebinder extends ConfigurationPropertiesRebinder {
private static final Logger LOGGER = LoggerFactory.getLogger(AffectedConfigurationPropertiesRebinder.class);
private ApplicationContext applicationContext;
private Map<String, ConfigurationPropertiesBean> propertiesBeans = new HashMap<>();
private final Map<String, Map<String, Object>> propertiesBeanDefaultValues = new ConcurrentHashMap<>();
public AffectedConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {
@ -125,8 +128,7 @@ public class AffectedConfigurationPropertiesRebinder extends ConfigurationProper
for (ConfigurationPropertiesBean propertiesBean : propertiesBeans.values()) {
Map<String, Object> defaultValues = new HashMap<>();
try {
Object instance = propertiesBean.getInstance().getClass().getDeclaredConstructor((Class<?>[]) null)
.newInstance();
Object instance = propertiesBean.getInstance().getClass().getDeclaredConstructor((Class<?>[]) null).newInstance();
ReflectionUtils.doWithFields(instance.getClass(), field -> {
try {
field.setAccessible(true);

@ -29,10 +29,13 @@ import org.springframework.core.env.Environment;
* @author juanyinyang
*/
public interface PolarisConfigCustomExtensionLayer {
boolean isEnabled();
void initRegisterConfig(PolarisConfigPropertyAutoRefresher polarisConfigPropertyAutoRefresher);
void initConfigFiles(Environment environment, CompositePropertySource compositePropertySource, PolarisPropertySourceManager polarisPropertySourceManager, ConfigFileService configFileService);
void initConfigFiles(Environment environment, CompositePropertySource compositePropertySource, ConfigFileService configFileService);
void executeAfterLocateConfigReturning(CompositePropertySource compositePropertySource);
boolean executeRegisterPublishChangeListener(PolarisPropertySource polarisPropertySource);
}

@ -13,7 +13,6 @@
* 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;
@ -65,39 +64,40 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
private final ConfigFileService configFileService;
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final Environment environment;
// this class provides customized logic for some customers to configure special business group files
private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer();
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties, PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, PolarisPropertySourceManager polarisPropertySourceManager, Environment environment) {
public PolarisConfigFileLocator(PolarisConfigProperties polarisConfigProperties,
PolarisContextProperties polarisContextProperties, ConfigFileService configFileService, Environment environment) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisContextProperties = polarisContextProperties;
this.configFileService = configFileService;
this.polarisPropertySourceManager = polarisPropertySourceManager;
this.environment = environment;
}
@Override
public PropertySource<?> locate(Environment environment) {
CompositePropertySource compositePropertySource = new CompositePropertySource(POLARIS_CONFIG_PROPERTY_SOURCE_NAME);
try {
// load custom config extension files
initCustomPolarisConfigExtensionFiles(compositePropertySource);
// load spring boot default config files
initInternalConfigFiles(compositePropertySource);
// load custom config files
List<ConfigFileGroup> configFileGroups = polarisConfigProperties.getGroups();
if (CollectionUtils.isEmpty(configFileGroups)) {
if (polarisConfigProperties.isEnabled()) {
CompositePropertySource compositePropertySource = new CompositePropertySource(POLARIS_CONFIG_PROPERTY_SOURCE_NAME);
try {
// load custom config extension files
initCustomPolarisConfigExtensionFiles(compositePropertySource);
// load spring boot default config files
initInternalConfigFiles(compositePropertySource);
// load custom config files
List<ConfigFileGroup> configFileGroups = polarisConfigProperties.getGroups();
if (CollectionUtils.isEmpty(configFileGroups)) {
return compositePropertySource;
}
initCustomPolarisConfigFiles(compositePropertySource, configFileGroups);
return compositePropertySource;
}
initCustomPolarisConfigFiles(compositePropertySource, configFileGroups);
return compositePropertySource;
}
finally {
afterLocatePolarisConfigExtension(compositePropertySource);
finally {
afterLocatePolarisConfigExtension(compositePropertySource);
}
}
return null;
}
private void initCustomPolarisConfigExtensionFiles(CompositePropertySource compositePropertySource) {
@ -105,7 +105,7 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
LOGGER.debug("[SCT Config] PolarisConfigCustomExtensionLayer is not init, ignore the following execution steps");
return;
}
polarisConfigCustomExtensionLayer.initConfigFiles(environment, compositePropertySource, polarisPropertySourceManager, configFileService);
polarisConfigCustomExtensionLayer.initConfigFiles(environment, compositePropertySource, configFileService);
}
private void afterLocatePolarisConfigExtension(CompositePropertySource compositePropertySource) {
@ -117,6 +117,9 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
}
private void initInternalConfigFiles(CompositePropertySource compositePropertySource) {
if (!polarisConfigProperties.isInternalEnabled()) {
return;
}
List<ConfigFileMetadata> internalConfigFiles = getInternalConfigFiles();
for (ConfigFileMetadata configFile : internalConfigFiles) {
@ -124,7 +127,7 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
compositePropertySource.addPropertySource(polarisPropertySource);
polarisPropertySourceManager.addPropertySource(polarisPropertySource);
PolarisPropertySourceManager.addPropertySource(polarisPropertySource);
LOGGER.info("[SCT Config] Load and inject polaris config file. file = {}", configFile);
}
@ -191,8 +194,12 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
String namespace = polarisContextProperties.getNamespace();
for (ConfigFileGroup configFileGroup : configFileGroups) {
String group = configFileGroup.getName();
String groupNamespace = configFileGroup.getNamespace();
if (!StringUtils.hasText(groupNamespace)) {
groupNamespace = namespace;
}
String group = configFileGroup.getName();
if (!StringUtils.hasText(group)) {
throw new IllegalArgumentException("polaris config group name cannot be empty.");
}
@ -203,26 +210,26 @@ public class PolarisConfigFileLocator implements PropertySourceLocator {
}
for (String fileName : files) {
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(namespace, group, fileName);
PolarisPropertySource polarisPropertySource = loadPolarisPropertySource(groupNamespace, group, fileName);
compositePropertySource.addPropertySource(polarisPropertySource);
polarisPropertySourceManager.addPropertySource(polarisPropertySource);
PolarisPropertySourceManager.addPropertySource(polarisPropertySource);
LOGGER.info("[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}, fileName = {}", namespace, group, fileName);
LOGGER.info("[SCT Config] Load and inject polaris config file success. namespace = {}, group = {}, fileName = {}", groupNamespace, group, fileName);
}
}
}
private PolarisPropertySource loadPolarisPropertySource(String namespace, String group, String fileName) {
ConfigKVFile configKVFile;
// unknown extension is resolved as properties file
if (ConfigFileFormat.isPropertyFile(fileName) || ConfigFileFormat.isUnknownFile(fileName)) {
configKVFile = configFileService.getConfigPropertiesFile(namespace, group, fileName);
}
else if (ConfigFileFormat.isYamlFile(fileName)) {
// unknown extension is resolved as yaml file
if (ConfigFileFormat.isYamlFile(fileName) || ConfigFileFormat.isUnknownFile(fileName)) {
configKVFile = configFileService.getConfigYamlFile(namespace, group, fileName);
}
else if (ConfigFileFormat.isPropertyFile(fileName)) {
configKVFile = configFileService.getConfigPropertiesFile(namespace, group, fileName);
}
else {
LOGGER.warn("[SCT Config] Unsupported config file. namespace = {}, group = {}, fileName = {}", namespace, group, fileName);

@ -18,14 +18,17 @@
package com.tencent.cloud.polaris.config.adapter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.logger.PolarisConfigLoggerContext;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeListener;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import com.tencent.polaris.configuration.client.internal.CompositeConfigFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -38,7 +41,7 @@ import org.springframework.util.CollectionUtils;
* 1. Listen to the Polaris server configuration publishing event 2. Write the changed
* configuration content to propertySource 3. Refresh the context through contextRefresher
*
* @author lepdou 2022-03-28
* @author lepdou
*/
public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationListener<ApplicationReadyEvent>, PolarisConfigPropertyRefresher {
@ -46,16 +49,13 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisPropertySourceManager polarisPropertySourceManager;
private final AtomicBoolean registered = new AtomicBoolean(false);
// this class provides customized logic for some customers to configure special business group files
private final PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer = PolarisServiceLoaderUtil.getPolarisConfigCustomExtensionLayer();
public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager) {
public PolarisConfigPropertyAutoRefresher(PolarisConfigProperties polarisConfigProperties) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
}
@Override
@ -68,7 +68,7 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
return;
}
List<PolarisPropertySource> polarisPropertySources = polarisPropertySourceManager.getAllPropertySources();
List<PolarisPropertySource> polarisPropertySources = PolarisPropertySourceManager.getAllPropertySources();
if (CollectionUtils.isEmpty(polarisPropertySources)) {
return;
}
@ -82,8 +82,18 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
// register polaris config publish event
for (PolarisPropertySource polarisPropertySource : polarisPropertySources) {
registerPolarisConfigPublishChangeListener(polarisPropertySource);
customRegisterPolarisConfigPublishChangeListener(polarisPropertySource);
if (polarisPropertySource.getConfigKVFile() instanceof CompositeConfigFile) {
CompositeConfigFile configKVFile = (CompositeConfigFile) polarisPropertySource.getConfigKVFile();
for (ConfigKVFile cf : configKVFile.getConfigKVFiles()) {
PolarisPropertySource p = new PolarisPropertySource(cf.getNamespace(), cf.getFileGroup(), cf.getFileName(), cf, new HashMap<>());
registerPolarisConfigPublishChangeListener(p);
customRegisterPolarisConfigPublishChangeListener(p);
}
}
else {
registerPolarisConfigPublishChangeListener(polarisPropertySource);
customRegisterPolarisConfigPublishChangeListener(polarisPropertySource);
}
}
}
@ -96,6 +106,7 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
}
public void registerPolarisConfigPublishChangeListener(PolarisPropertySource polarisPropertySource) {
LOGGER.info("{} will register polaris config publish listener", polarisPropertySource.getPropertySourceName());
polarisPropertySource.getConfigKVFile()
.addChangeListener((ConfigKVFileChangeListener) configKVFileChangeEvent -> {
@ -144,4 +155,12 @@ public abstract class PolarisConfigPropertyAutoRefresher implements ApplicationL
}
polarisConfigCustomExtensionLayer.executeRegisterPublishChangeListener(polarisPropertySource);
}
/**
* Just for junit test.
* @param registered if the polaris config property auto refresh is registered
*/
public void setRegistered(boolean registered) {
this.registered.set(registered);
}
}

@ -20,6 +20,7 @@ package com.tencent.cloud.polaris.config.adapter;
import java.util.Map;
import com.tencent.cloud.polaris.config.utils.PolarisPropertySourceUtils;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import org.springframework.core.env.MapPropertySource;
@ -40,7 +41,7 @@ public class PolarisPropertySource extends MapPropertySource {
private final ConfigKVFile configKVFile;
public PolarisPropertySource(String namespace, String group, String fileName, ConfigKVFile configKVFile, Map<String, Object> source) {
super(namespace + "-" + group + "-" + fileName, source);
super(PolarisPropertySourceUtils.generateName(namespace, group, fileName), source);
this.namespace = namespace;
this.group = group;
@ -64,7 +65,7 @@ public class PolarisPropertySource extends MapPropertySource {
return namespace + "-" + group + "-" + fileName;
}
ConfigKVFile getConfigKVFile() {
public ConfigKVFile getConfigKVFile() {
return configKVFile;
}

@ -28,15 +28,25 @@ import java.util.concurrent.ConcurrentHashMap;
*
* @author lepdou 2022-03-28
*/
public class PolarisPropertySourceManager {
public final class PolarisPropertySourceManager {
private final Map<String, PolarisPropertySource> polarisPropertySources = new ConcurrentHashMap<>();
private static final Map<String, PolarisPropertySource> polarisPropertySources = new ConcurrentHashMap<>();
public void addPropertySource(PolarisPropertySource polarisPropertySource) {
polarisPropertySources.putIfAbsent(polarisPropertySource.getPropertySourceName(), polarisPropertySource);
private PolarisPropertySourceManager() {
}
public List<PolarisPropertySource> getAllPropertySources() {
public static void addPropertySource(PolarisPropertySource polarisPropertySource) {
polarisPropertySources.put(polarisPropertySource.getPropertySourceName(), polarisPropertySource);
}
public static List<PolarisPropertySource> getAllPropertySources() {
return new ArrayList<>(polarisPropertySources.values());
}
/**
* Just for test.
*/
public static void clearPropertySources() {
polarisPropertySources.clear();
}
}

@ -59,9 +59,8 @@ public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropert
private TypeConverter typeConverter;
public PolarisRefreshAffectedContextRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager, SpringValueRegistry springValueRegistry,
PlaceholderHelper placeholderHelper) {
super(polarisConfigProperties, polarisPropertySourceManager);
SpringValueRegistry springValueRegistry, PlaceholderHelper placeholderHelper) {
super(polarisConfigProperties);
this.springValueRegistry = springValueRegistry;
this.placeholderHelper = placeholderHelper;
}
@ -100,7 +99,7 @@ public class PolarisRefreshAffectedContextRefresher extends PolarisConfigPropert
* 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)
* String, Set, TypeConverter)
*/
private Object resolvePropertyValue(SpringValue springValue) {
// value will never be null

@ -35,12 +35,16 @@ public class PolarisRefreshEntireContextRefresher extends PolarisConfigPropertyA
private final ContextRefresher contextRefresher;
public PolarisRefreshEntireContextRefresher(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager,
ContextRefresher contextRefresher) {
super(polarisConfigProperties, polarisPropertySourceManager);
super(polarisConfigProperties);
this.contextRefresher = contextRefresher;
}
@Override
public void refreshSpringValue(String changedKey) {
// do nothing,all config will be refreshed by contextRefresher.refresh
}
@Override
public void refreshConfigurationProperties(Set<String> changeKeys) {
contextRefresher.refresh();

@ -30,20 +30,25 @@ import org.slf4j.LoggerFactory;
public final class PolarisServiceLoaderUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisServiceLoaderUtil.class);
private PolarisServiceLoaderUtil() {
}
// this class provides customized logic for some customers to configure special business group files
private static PolarisConfigCustomExtensionLayer polarisConfigCustomExtensionLayer;
static {
ServiceLoader<PolarisConfigCustomExtensionLayer> polarisConfigCustomExtensionLayerLoader = ServiceLoader.load(PolarisConfigCustomExtensionLayer.class);
Iterator<PolarisConfigCustomExtensionLayer> polarisConfigCustomExtensionLayerIterator = polarisConfigCustomExtensionLayerLoader.iterator();
// Generally, there is only one implementation class. If there are multiple, the last one is loaded
while (polarisConfigCustomExtensionLayerIterator.hasNext()) {
polarisConfigCustomExtensionLayer = polarisConfigCustomExtensionLayerIterator.next();
LOGGER.info("[SCT Config] PolarisConfigFileLocator init polarisConfigCustomExtensionLayer:{}", polarisConfigCustomExtensionLayer);
PolarisConfigCustomExtensionLayer temp = polarisConfigCustomExtensionLayerIterator.next();
if (temp.isEnabled()) {
polarisConfigCustomExtensionLayer = temp;
LOGGER.info("[SCT Config] PolarisConfigFileLocator init polarisConfigCustomExtensionLayer:{}", polarisConfigCustomExtensionLayer);
}
}
}
private PolarisServiceLoaderUtil() {
}
public static PolarisConfigCustomExtensionLayer getPolarisConfigCustomExtensionLayer() {
return polarisConfigCustomExtensionLayer;
}

@ -108,7 +108,8 @@ public class PolarisConfigAnnotationProcessor implements BeanPostProcessor, Prio
Set<String> interestedKeys =
annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
Set<String> interestedKeyPrefixes =
annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes) : null;
annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes)
: null;
addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
}

@ -15,7 +15,6 @@
* specific language governing permissions and limitations under the License.
*
*/
package com.tencent.cloud.polaris.config.config;
import java.util.List;
@ -27,6 +26,8 @@ import java.util.List;
*/
public class ConfigFileGroup {
private String namespace;
/**
* group name.
*/
@ -37,6 +38,14 @@ public class ConfigFileGroup {
*/
private List<String> files;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getName() {
return name;
}
@ -55,6 +64,10 @@ public class ConfigFileGroup {
@Override
public String toString() {
return "ConfigFileGroup{" + "name='" + name + '\'' + ", file=" + files + '}';
return "ConfigFileGroup{" +
"namespace='" + namespace + '\'' +
", name='" + name + '\'' +
", files=" + files +
'}';
}
}

@ -50,6 +50,8 @@ public class PolarisConfigProperties {
@Value("${spring.cloud.polaris.config.port:#{'8093'}}")
private int port = 8093;
private String token;
/**
* Whether to automatically update to the spring context when the configuration file.
* is updated
@ -89,6 +91,11 @@ public class PolarisConfigProperties {
*/
private String localFileRootPath = "./polaris/backup/config";
/**
* If internal config file enabled.
*/
private boolean internalEnabled = true;
public boolean isEnabled() {
return enabled;
}
@ -113,6 +120,14 @@ public class PolarisConfigProperties {
this.port = port;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isAutoRefresh() {
return autoRefresh;
}
@ -168,4 +183,30 @@ public class PolarisConfigProperties {
public void setLocalFileRootPath(String localFileRootPath) {
this.localFileRootPath = localFileRootPath;
}
public boolean isInternalEnabled() {
return internalEnabled;
}
public void setInternalEnabled(boolean internalEnabled) {
this.internalEnabled = internalEnabled;
}
@Override
public String toString() {
return "PolarisConfigProperties{" +
"enabled=" + enabled +
", address='" + address + '\'' +
", port=" + port +
", token='" + token + '\'' +
", autoRefresh=" + autoRefresh +
", shutdownIfConnectToConfigServerFailed=" + shutdownIfConnectToConfigServerFailed +
", preference=" + preference +
", refreshType=" + refreshType +
", groups=" + groups +
", dataSource='" + dataSource + '\'' +
", localFileRootPath='" + localFileRootPath + '\'' +
", internalEnabled=" + internalEnabled +
'}';
}
}

@ -33,15 +33,13 @@ import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
*
* @author shuiqingliu
**/
@Endpoint(id = "polaris-config")
@Endpoint(id = "polarisconfig")
public class PolarisConfigEndpoint {
private final PolarisConfigProperties polarisConfigProperties;
private final PolarisPropertySourceManager polarisPropertySourceManager;
public PolarisConfigEndpoint(PolarisConfigProperties polarisConfigProperties, PolarisPropertySourceManager polarisPropertySourceManager) {
public PolarisConfigEndpoint(PolarisConfigProperties polarisConfigProperties) {
this.polarisConfigProperties = polarisConfigProperties;
this.polarisPropertySourceManager = polarisPropertySourceManager;
}
@ReadOperation
@ -49,7 +47,7 @@ public class PolarisConfigEndpoint {
Map<String, Object> configInfo = new HashMap<>();
configInfo.put("PolarisConfigProperties", polarisConfigProperties);
List<PolarisPropertySource> propertySourceList = polarisPropertySourceManager.getAllPropertySources();
List<PolarisPropertySource> propertySourceList = PolarisPropertySourceManager.getAllPropertySources();
configInfo.put("PolarisPropertySource", propertySourceList);
return configInfo;

@ -18,7 +18,6 @@
package com.tencent.cloud.polaris.config.endpoint;
import com.tencent.cloud.polaris.config.ConditionalOnPolarisConfigEnabled;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
@ -41,8 +40,7 @@ public class PolarisConfigEndpointAutoConfiguration {
@Bean
@ConditionalOnAvailableEndpoint
@ConditionalOnMissingBean
public PolarisConfigEndpoint polarisConfigEndpoint(PolarisConfigProperties polarisConfigProperties,
PolarisPropertySourceManager polarisPropertySourceManager) {
return new PolarisConfigEndpoint(polarisConfigProperties, polarisPropertySourceManager);
public PolarisConfigEndpoint polarisConfigEndpoint(PolarisConfigProperties polarisConfigProperties) {
return new PolarisConfigEndpoint(polarisConfigProperties);
}
}

@ -66,7 +66,6 @@ public final class PolarisConfigChangeEventListener implements ApplicationListen
*/
@Override
public void onApplicationEvent(@NonNull ApplicationEvent event) {
// Initialize application all environment properties .
if (event instanceof ApplicationStartedEvent && started.compareAndSet(false, true)) {
ApplicationStartedEvent applicationStartedEvent = (ApplicationStartedEvent) event;

@ -52,11 +52,9 @@ import static com.tencent.polaris.configuration.api.core.ChangeType.MODIFIED;
* <p>This source file was reference from:
* <code><a href=https://github.com/apolloconfig/apollo/blob/master/apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java>
* AbstractConfig</a></code>
*
* @author Palmer Xu 2022-06-06
*/
public final class PolarisConfigListenerContext {
/**
* Logger instance.
*/

@ -21,7 +21,6 @@ package com.tencent.cloud.polaris.config.listener;
import java.util.Collections;
import com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.config.enums.RefreshType;
@ -44,7 +43,7 @@ import org.springframework.lang.NonNull;
import static com.tencent.cloud.polaris.config.condition.ReflectRefreshTypeCondition.POLARIS_CONFIG_REFRESH_TYPE;
/**
* When {@link com.tencent.cloud.polaris.config.adapter.PolarisConfigRefreshScopeAnnotationDetector} detects that
* When {@link PolarisConfigRefreshScopeAnnotationDetector} detects that
* the annotation {@code @RefreshScope} exists and is used, but the config refresh type
* {@code spring.cloud.polaris.config.refresh-type} is still {@code RefreshType.REFLECT}, then the framework will
* automatically switch the config refresh type to {@code RefreshType.REFRESH_CONTEXT}.
@ -112,7 +111,7 @@ public class PolarisConfigRefreshOptimizationListener implements ApplicationList
beanFactory.removeBeanDefinition(REFLECT_REBINDER_BEAN_NAME);
}
catch (BeansException e) {
// If there is a removeBean exception in this code, do not affect the main process startup. Some user usage may cause the polarisReflectPropertySourceAutoRefresher to not load, and the removeBeanDefinition will report an error
// If there is a removeBean exception in this code, do not affect the main process startup. Some user usage may cause the polarisReflectPropertySourceAutoRefresher to not load, and the removeBeanDefinition will report an error
LOGGER.debug("removeRelatedBeansOfReflect occur error:", e);
}
}
@ -122,12 +121,10 @@ public class PolarisConfigRefreshOptimizationListener implements ApplicationList
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(PolarisRefreshEntireContextRefresher.class);
PolarisConfigProperties polarisConfigProperties = beanFactory.getBean(PolarisConfigProperties.class);
PolarisPropertySourceManager polarisPropertySourceManager = beanFactory.getBean(PolarisPropertySourceManager.class);
ContextRefresher contextRefresher = beanFactory.getBean(ContextRefresher.class);
ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, polarisConfigProperties);
constructorArgumentValues.addIndexedArgumentValue(1, polarisPropertySourceManager);
constructorArgumentValues.addIndexedArgumentValue(2, contextRefresher);
constructorArgumentValues.addIndexedArgumentValue(1, contextRefresher);
beanFactory.registerBeanDefinition(REFRESH_CONTEXT_REFRESHER_BEAN_NAME, beanDefinition);
}

@ -32,7 +32,7 @@ import org.springframework.context.ApplicationListener;
public class PolarisConfigLoggerApplicationListener implements ApplicationListener<ApplicationEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(PolarisConfigLoggerApplicationListener.class);
/**
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
* @see ApplicationListener#onApplicationEvent(ApplicationEvent)
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {

@ -13,7 +13,6 @@
* 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.annotation;

@ -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.config.tsf;
import com.tencent.cloud.common.tsf.ConditionalOnTsfConsulEnabled;
import com.tencent.cloud.polaris.config.ConditionalOnPolarisConfigEnabled;
import com.tencent.cloud.polaris.config.tsf.controller.PolarisAdaptorTsfConfigController;
import com.tencent.tsf.consul.config.watch.TsfConsulConfigRefreshEventListener;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author juanyinyang
* @Date Jul 23, 2023 3:52:48 PM
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnTsfConsulEnabled
@ConditionalOnPolarisConfigEnabled
public class PolarisAdaptorTsfConfigAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.cloud.consul.config.watch.enabled", matchIfMissing = true)
public TsfConsulConfigRefreshEventListener polarisAdaptorTsfConsulRefreshEventListener() {
return new TsfConsulConfigRefreshEventListener();
}
/**
*
* 1Spring Cloud Consul ConfigConsul ConfigtsfConfigController
* 2@ConditionalOnPolarisConfigEnabled
* 3tsf.config.instance.released-config.lookup.enabled.
* @return polarisAdaptorTsfConfigController
*/
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "tsf.config.instance.released-config.lookup.enabled", matchIfMissing = true)
public PolarisAdaptorTsfConfigController polarisAdaptorTsfConfigController() {
return new PolarisAdaptorTsfConfigController();
}
}

@ -0,0 +1,71 @@
/*
* 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.tsf.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author juanyinyang
* @Date 202382 5:08:29
*/
@RestController
public class PolarisAdaptorTsfConfigController {
private static final Logger LOG = LoggerFactory.getLogger(PolarisAdaptorTsfConfigController.class);
@Autowired
Environment environment;
public PolarisAdaptorTsfConfigController() {
LOG.info("init PolarisAdaptorTsfConfigController");
}
/**
* TSFSDK.
* @return config map
*/
@RequestMapping("/tsf/innerApi/config/findAllConfig")
public Map<String, Object> findAllConfig() {
List<PolarisPropertySource> propertySourceList = PolarisPropertySourceManager.getAllPropertySources();
Set<String> keys = new HashSet<>();
for (PolarisPropertySource propertySource : propertySourceList) {
keys.addAll(Arrays.asList(propertySource.getPropertyNames()));
}
return keys.stream()
.collect(HashMap::new, (map, key) -> map.put(key, environment.getProperty(key)), HashMap::putAll);
}
}

@ -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.polaris.config.tsf.encrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConfigEncryptAESProvider extends ConfigEncryptProvider {
private static final Logger log = LoggerFactory.getLogger(ConfigEncryptAESProvider.class);
@Override
public String encrypt(String content, String password) {
try {
return EncryptAlgorithm.AES256.encrypt(content, password);
}
catch (Exception e) {
log.error("Error on encrypting.", e);
throw e;
}
}
@Override
public String decrypt(String encryptedContent, String password) {
try {
return EncryptAlgorithm.AES256.decrypt(encryptedContent, password);
}
catch (Exception e) {
log.error("Error on decrypting.", e);
throw e;
}
}
}

@ -15,27 +15,30 @@
* specific language governing permissions and limitations under the License.
*/
package com.tencent.cloud.polaris.router.spi;
import java.util.Map;
import java.util.Set;
import feign.RequestTemplate;
import org.springframework.core.Ordered;
package com.tencent.cloud.polaris.config.tsf.encrypt;
/**
* Router label resolver for feign request.
* @author lepdou 2022-07-20
* TSF .
*
* @author hongweizhu
*/
public interface FeignRouterLabelResolver extends Ordered {
public abstract class ConfigEncryptProvider {
/**
* .
*
* @param content
* @param password
* @return
*/
public abstract String encrypt(String content, String password);
/**
* Resolve labels from feign request. User can customize expression parser to extract labels.
* .
*
* @param requestTemplate the feign request.
* @param expressionLabelKeys the expression labels which are configured in router rule.
* @return resolved labels
* @param encryptedContent
* @param password
* @return
*/
Map<String, String> resolve(RequestTemplate requestTemplate, Set<String> expressionLabelKeys);
public abstract String decrypt(String encryptedContent, String password);
}

@ -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.polaris.config.tsf.encrypt;
public final class ConfigEncryptProviderFactory {
private static ConfigEncryptProvider configEncryptProvider = null;
private ConfigEncryptProviderFactory() {
}
public static ConfigEncryptProvider getInstance() {
if (null == configEncryptProvider) {
try {
Class<?> providerClass = Class.forName(EncryptConfig.getProviderClass());
configEncryptProvider = (ConfigEncryptProvider) providerClass.newInstance();
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
return configEncryptProvider;
}
}

@ -0,0 +1,146 @@
/*
* 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.tsf.encrypt;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
public class EncryptAlgorithm {
public static class AES256 {
/**
* .
*
* @param content
* @param password
* @return
*/
public static final String encrypt(String content, String password) {
if (null == password || "".equals(password)) {
throw new PasswordNotFoundException();
}
try {
// AES SK生成器
KeyGenerator kgen = KeyGenerator.getInstance("AES");
// SHA-256摘要密钥后生成安全随机数
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(SHA256.encode(password));
kgen.init(256, sr);
// 生成秘密(对称)密钥
SecretKey secretKey = kgen.generateKey();
// 返回基本编码格式的密钥
byte[] enCodeFormat = secretKey.getEncoded();
// 根据给定的字节数组构造一个密钥。enCodeFormat密钥内容"AES":与给定的密钥内容相关联的密钥算法的名称
SecretKeySpec skSpec = new SecretKeySpec(enCodeFormat, "AES");
// 将提供程序添加到下一个可用位置
Security.addProvider(new BouncyCastleProvider());
// 创建一个实现指定转换的 Cipher对象该转换由指定的提供程序提供。
// "AES/ECB/PKCS7Padding":转换的名称;"BC":提供程序的名称
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
// 初始化cipher加密模式
cipher.init(Cipher.ENCRYPT_MODE, skSpec);
byte[] byteContent = content.getBytes(StandardCharsets.UTF_8);
byte[] cryptograph = cipher.doFinal(byteContent);
byte[] enryptedContent = Base64.encode(cryptograph);
return new String(enryptedContent);
}
catch (Exception e) {
throw new RuntimeException("Failed encrypt.", e);
}
}
/**
* .
*
* @param encryptedContent
* @param password
* @return
*/
public static final String decrypt(String encryptedContent, String password) {
if (null == password || "".equals(password)) {
throw new PasswordNotFoundException();
}
try {
// AES SK生成器
KeyGenerator kgen = KeyGenerator.getInstance("AES");
// SHA-256摘要密钥后生成安全随机数
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(SHA256.encode(password));
kgen.init(256, sr);
// 生成秘密(对称)密钥
SecretKey secretKey = kgen.generateKey();
// 返回基本编码格式的密钥
byte[] enCodeFormat = secretKey.getEncoded();
// 根据给定的字节数组构造一个密钥。enCodeFormat密钥内容"AES":与给定的密钥内容相关联的密钥算法的名称
SecretKeySpec skSpec = new SecretKeySpec(enCodeFormat, "AES");
// 将提供程序添加到下一个可用位置
Security.addProvider(new BouncyCastleProvider());
// 创建一个实现指定转换的 Cipher对象该转换由指定的提供程序提供。
// "AES/ECB/PKCS7Padding":转换的名称;"BC":提供程序的名称
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
// 初始化cipher解密模式
cipher.init(Cipher.DECRYPT_MODE, skSpec);
byte[] result = cipher.doFinal(Base64.decode(encryptedContent.getBytes(StandardCharsets.UTF_8)));
return new String(result);
}
catch (Exception e) {
throw new RuntimeException("Failed decrypt.", e);
}
}
}
public static class SHA256 {
/**
* SHA-256.
*
* @param content
* @return
* @throws NoSuchAlgorithmException
*/
public static byte[] encode(String content) throws NoSuchAlgorithmException {
MessageDigest digester = MessageDigest.getInstance("SHA-256");
digester.update(content.getBytes(StandardCharsets.UTF_8));
return digester.digest();
}
}
public static class PasswordNotFoundException extends RuntimeException {
/**
* serialVersionUID.
*/
private static final long serialVersionUID = -2843758461182470411L;
public PasswordNotFoundException() {
super("Password not found.");
}
}
}

@ -0,0 +1,115 @@
/*
* 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.tsf.encrypt;
import org.springframework.util.StringUtils;
public final class EncryptConfig {
private static final String PASSWORD_KEY = "tsf_config_encrypt_password";
/**
* .
*/
public static String ENCRYPT_PREFIX = "ENC(";
/**
* .
*/
public static String ENCRYPT_SUFFIX = ")";
/**
* .
*/
private static String password;
/**
* .
*/
private static String providerClass = "com.tencent.cloud.polaris.config.tsf.encrypt.ConfigEncryptAESProvider";
static {
// 环境变量
if (null != System.getenv(PASSWORD_KEY)) {
password = System.getenv(PASSWORD_KEY);
}
// JVM参数
if (null != System.getProperty(PASSWORD_KEY)) {
password = System.getProperty(PASSWORD_KEY);
}
}
private EncryptConfig() {
}
/**
* password .
* @return truefalse
*/
public static Boolean getEnabled() {
return !StringUtils.isEmpty(password);
}
public static String getPassword() {
return EncryptConfig.password;
}
public static void setPassword(String password) {
EncryptConfig.password = password;
}
public static ConfigEncryptProvider getProvider() {
return ConfigEncryptProviderFactory.getInstance();
}
public static String getProviderClass() {
return providerClass;
}
public static void setProviderClass(String providerClass) {
EncryptConfig.providerClass = providerClass;
}
/**
* .
*
* @param content
* @return truefalse
*/
public static Boolean needDecrypt(Object content) {
if (null == content) {
return false;
}
else {
String stringValue = String.valueOf(content);
return stringValue.startsWith(ENCRYPT_PREFIX) && stringValue.endsWith(ENCRYPT_SUFFIX);
}
}
/**
* .
*
* @param content
* @return
*/
public static String realContent(Object content) {
if (null != content) {
String stringValue = String.valueOf(content);
return stringValue.substring(ENCRYPT_PREFIX.length(), stringValue.length() - ENCRYPT_SUFFIX.length());
}
return null;
}
}

@ -0,0 +1,34 @@
/*
* 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.utils;
/**
* Utils for PolarisPropertySource.
*
* @author Haotian Zhang
*/
public final class PolarisPropertySourceUtils {
private PolarisPropertySourceUtils() {
}
public static String generateName(String namespace, String group, String fileName) {
return namespace + "-" + group + "-" + fileName;
}
}

@ -0,0 +1,28 @@
/*
* 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.tsf.consul.config.watch;
public interface ConfigChangeCallback {
/**
* .
* @param lastConfigProperty
* @param newConfigProperty
*/
void callback(ConfigProperty lastConfigProperty, ConfigProperty newConfigProperty);
}

@ -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.tsf.consul.config.watch;
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.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ConfigChangeListener {
String prefix() default "";
String[] value() default {};
boolean async() default false;
}

@ -0,0 +1,46 @@
/*
* 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.tsf.consul.config.watch;
public class ConfigProperty {
private String key;
private Object value;
public ConfigProperty(String key, Object value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}

@ -0,0 +1,122 @@
/*
* 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.tsf.consul.config.watch;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.tencent.cloud.polaris.config.listener.ConfigChangeEvent;
import com.tencent.cloud.polaris.config.listener.PolarisConfigListenerContext;
import com.tencent.cloud.polaris.config.listener.SyncConfigChangeListener;
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.lang.NonNull;
import org.springframework.util.StringUtils;
public class TsfConsulConfigRefreshEventListener implements BeanPostProcessor, PriorityOrdered {
private static final String DOT = ".";
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public Object postProcessBeforeInitialization(@NonNull Object obj, @NonNull String beanName) throws BeansException {
return obj;
}
@Override
public Object postProcessAfterInitialization(@NonNull Object obj, @NonNull String beanName) throws BeansException {
Class<?> clz = obj.getClass();
if (!clz.isAnnotationPresent(ConfigChangeListener.class) || !ConfigChangeCallback.class.isAssignableFrom(clz)) {
return obj;
}
ConfigChangeListener targetAnno = clz.getAnnotation(ConfigChangeListener.class);
String watchedPrefix = targetAnno.prefix();
String[] watchedConfirmedValue = targetAnno.value();
boolean isAsync = targetAnno.async();
if (watchedConfirmedValue.length == 0 && StringUtils.isEmpty(watchedPrefix)) {
return obj;
}
ConfigChangeCallback bean = (ConfigChangeCallback) obj;
com.tencent.cloud.polaris.config.listener.ConfigChangeListener listener = new SyncConfigChangeListener() {
@Override
public void onChange(ConfigChangeEvent changeEvent) {
List<TsfCallbackParam> paramList = parseConfigChangeEventToTsfCallbackParam(changeEvent);
for (TsfCallbackParam param : paramList) {
if (isAsync()) {
PolarisConfigListenerContext.executor()
.execute(() -> bean.callback(param.oldValue, param.newValue));
}
else {
bean.callback(param.oldValue, param.newValue);
}
}
}
@Override
public boolean isAsync() {
return isAsync;
}
};
Set<String> interestedKeys = new HashSet<>();
Set<String> interestedKeyPrefixes = new HashSet<>();
if (watchedConfirmedValue.length > 0) {
for (String value : watchedConfirmedValue) {
interestedKeys.add(StringUtils.isEmpty(watchedPrefix) ? value : watchedPrefix + DOT + value);
}
}
else {
interestedKeyPrefixes.add(watchedPrefix);
}
PolarisConfigListenerContext.addChangeListener(listener, interestedKeys, interestedKeyPrefixes);
return bean;
}
private List<TsfCallbackParam> parseConfigChangeEventToTsfCallbackParam(ConfigChangeEvent event) {
List<TsfCallbackParam> result = new ArrayList<>();
Set<String> changedKeys = event.changedKeys();
for (String changedKey : changedKeys) {
ConfigProperty oldValue = new ConfigProperty(changedKey, event.getChange(changedKey).getOldValue());
ConfigProperty newValue = new ConfigProperty(changedKey, event.getChange(changedKey).getNewValue());
TsfCallbackParam param = new TsfCallbackParam(oldValue, newValue);
result.add(param);
}
return result;
}
static class TsfCallbackParam {
ConfigProperty oldValue;
ConfigProperty newValue;
TsfCallbackParam(ConfigProperty oldValue, ConfigProperty newValue) {
this.oldValue = oldValue;
this.newValue = newValue;
}
}
}

@ -1,5 +1,8 @@
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration,\
com.tencent.cloud.polaris.config.endpoint.PolarisConfigEndpointAutoConfiguration
com.tencent.cloud.polaris.config.endpoint.PolarisConfigEndpointAutoConfiguration,\
com.tencent.cloud.polaris.config.tsf.PolarisAdaptorTsfConfigAutoConfiguration,\
com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration

@ -29,6 +29,7 @@ import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import com.tencent.cloud.polaris.context.config.PolarisContextProperties;
import com.tencent.polaris.configuration.api.core.ConfigFileService;
import com.tencent.polaris.configuration.api.core.ConfigKVFile;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@ -42,8 +43,7 @@ import static org.mockito.Mockito.when;
/**
* test for {@link PolarisConfigFileLocator}.
*
* @author lepdou 2022-06-11
*@author lepdou 2022-06-11
*/
@ExtendWith(MockitoExtension.class)
public class PolarisConfigFileLocatorTest {
@ -57,14 +57,17 @@ public class PolarisConfigFileLocatorTest {
@Mock
private ConfigFileService configFileService;
@Mock
private PolarisPropertySourceManager polarisPropertySourceManager;
@Mock
private Environment environment;
@BeforeEach
public void setUp() {
PolarisPropertySourceManager.clearPropertySources();
}
@Test
public void testLoadApplicationPropertiesFile() {
PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties,
configFileService, polarisPropertySourceManager, environment);
configFileService, environment);
when(polarisContextProperties.getNamespace()).thenReturn(testNamespace);
when(polarisContextProperties.getService()).thenReturn(testServiceName);
@ -86,7 +89,9 @@ public class PolarisConfigFileLocatorTest {
when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yml")).thenReturn(emptyConfigFile);
when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap.yaml")).thenReturn(emptyConfigFile);
when(polarisConfigProperties.isEnabled()).thenReturn(true);
when(polarisConfigProperties.getGroups()).thenReturn(null);
when(polarisConfigProperties.isInternalEnabled()).thenReturn(true);
when(environment.getActiveProfiles()).thenReturn(new String[] {});
PropertySource<?> propertySource = locator.locate(environment);
@ -99,7 +104,7 @@ public class PolarisConfigFileLocatorTest {
@Test
public void testActiveProfileFilesPriorityBiggerThanDefault() {
PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties,
configFileService, polarisPropertySourceManager, environment);
configFileService, environment);
when(polarisContextProperties.getNamespace()).thenReturn(testNamespace);
when(polarisContextProperties.getService()).thenReturn(testServiceName);
@ -133,7 +138,9 @@ public class PolarisConfigFileLocatorTest {
when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap-dev.yml")).thenReturn(emptyConfigFile);
when(configFileService.getConfigYamlFile(testNamespace, testServiceName, "bootstrap-dev.yaml")).thenReturn(emptyConfigFile);
when(polarisConfigProperties.isEnabled()).thenReturn(true);
when(polarisConfigProperties.getGroups()).thenReturn(null);
when(polarisConfigProperties.isInternalEnabled()).thenReturn(true);
when(environment.getActiveProfiles()).thenReturn(new String[] {"dev"});
PropertySource<?> propertySource = locator.locate(environment);
@ -146,7 +153,7 @@ public class PolarisConfigFileLocatorTest {
@Test
public void testGetCustomFiles() {
PolarisConfigFileLocator locator = new PolarisConfigFileLocator(polarisConfigProperties, polarisContextProperties,
configFileService, polarisPropertySourceManager, environment);
configFileService, environment);
when(polarisContextProperties.getNamespace()).thenReturn(testNamespace);
when(polarisContextProperties.getService()).thenReturn(testServiceName);
@ -170,7 +177,9 @@ public class PolarisConfigFileLocatorTest {
configFileGroup.setFiles(Lists.newArrayList(customFile1, customFile2));
customFiles.add(configFileGroup);
when(polarisConfigProperties.isEnabled()).thenReturn(true);
when(polarisConfigProperties.getGroups()).thenReturn(customFiles);
when(polarisConfigProperties.isInternalEnabled()).thenReturn(true);
when(environment.getActiveProfiles()).thenReturn(new String[] {});
// file1.properties

@ -24,7 +24,6 @@ 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;
@ -32,6 +31,7 @@ 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;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@ -59,8 +59,6 @@ public class PolarisPropertiesSourceAutoRefresherTest {
private final String testFileName = "application.properties";
@Mock
private PolarisConfigProperties polarisConfigProperties;
@Mock
private PolarisPropertySourceManager polarisPropertySourceManager;
@Mock
private SpringValueRegistry springValueRegistry;
@ -68,10 +66,14 @@ public class PolarisPropertiesSourceAutoRefresherTest {
@Mock
private PlaceholderHelper placeholderHelper;
@BeforeEach
public void setUp() {
PolarisPropertySourceManager.clearPropertySources();
}
@Test
public void testConfigFileChanged() throws Exception {
PolarisRefreshAffectedContextRefresher refresher = new PolarisRefreshAffectedContextRefresher(polarisConfigProperties,
polarisPropertySourceManager, springValueRegistry, placeholderHelper);
PolarisRefreshAffectedContextRefresher refresher = new PolarisRefreshAffectedContextRefresher(polarisConfigProperties, springValueRegistry, placeholderHelper);
ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class);
ConfigurableListableBeanFactory beanFactory = mock(ConfigurableListableBeanFactory.class);
TypeConverter typeConverter = mock(TypeConverter.class);
@ -99,7 +101,7 @@ public class PolarisPropertiesSourceAutoRefresherTest {
PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName,
file, content);
when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
PolarisPropertySourceManager.addPropertySource(polarisPropertySource);
ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED);
ConfigPropertyChangeInfo changeInfo2 = new ConfigPropertyChangeInfo("k4", null, "v4", ChangeType.ADDED);

@ -24,7 +24,6 @@ import java.util.Objects;
import com.tencent.cloud.polaris.config.PolarisConfigAutoConfiguration;
import com.tencent.cloud.polaris.config.PolarisConfigBootstrapAutoConfiguration;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshAffectedContextRefresher;
import com.tencent.cloud.polaris.config.adapter.PolarisRefreshEntireContextRefresher;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
@ -107,7 +106,6 @@ public class ConditionalOnReflectRefreshTypeTest {
.withPropertyValues("spring.cloud.polaris.config.enabled=true");
contextRunner.run(context -> {
assertThat(context).hasSingleBean(PolarisConfigProperties.class);
assertThat(context).hasSingleBean(PolarisPropertySourceManager.class);
assertThat(context).hasSingleBean(ContextRefresher.class);
assertThat(context).hasSingleBean(PolarisRefreshEntireContextRefresher.class);
});

@ -26,13 +26,13 @@ import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
import com.tencent.cloud.polaris.config.config.PolarisConfigProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
/**
* Test for polaris config endpoint.
@ -48,8 +48,11 @@ public class PolarisConfigEndpointTest {
@Mock
private PolarisConfigProperties polarisConfigProperties;
@Mock
private PolarisPropertySourceManager polarisPropertySourceManager;
@BeforeEach
public void setUp() {
PolarisPropertySourceManager.clearPropertySources();
}
@Test
public void testPolarisConfigEndpoint() {
@ -60,9 +63,9 @@ public class PolarisConfigEndpointTest {
MockedConfigKVFile file = new MockedConfigKVFile(content);
PolarisPropertySource polarisPropertySource = new PolarisPropertySource(testNamespace, testServiceName, testFileName,
file, content);
when(polarisPropertySourceManager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
PolarisPropertySourceManager.addPropertySource(polarisPropertySource);
PolarisConfigEndpoint endpoint = new PolarisConfigEndpoint(polarisConfigProperties, polarisPropertySourceManager);
PolarisConfigEndpoint endpoint = new PolarisConfigEndpoint(polarisConfigProperties);
Map<String, Object> info = endpoint.polarisConfig();
assertThat(polarisConfigProperties).isEqualTo(info.get("PolarisConfigProperties"));
assertThat(Lists.newArrayList(polarisPropertySource)).isEqualTo(info.get("PolarisPropertySource"));

@ -21,7 +21,6 @@ package com.tencent.cloud.polaris.config.listener;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Lists;
import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
@ -31,6 +30,7 @@ import com.tencent.cloud.polaris.config.enums.RefreshType;
import com.tencent.polaris.configuration.api.core.ChangeType;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
@ -77,6 +77,11 @@ public class PolarisConfigRefreshOptimizationListenerNotTriggeredTest {
@Autowired
private ConfigurableApplicationContext context;
@BeforeAll
static void beforeAll() {
PolarisPropertySourceManager.clearPropertySources();
}
@Test
public void testNotSwitchConfigRefreshType() {
RefreshType actualRefreshType = context.getEnvironment()
@ -100,12 +105,12 @@ public class PolarisConfigRefreshOptimizationListenerNotTriggeredTest {
PolarisPropertySource polarisPropertySource = new PolarisPropertySource(TEST_NAMESPACE, TEST_SERVICE_NAME, TEST_FILE_NAME,
file, content);
PolarisPropertySourceManager manager = context.getBean(PolarisPropertySourceManager.class);
when(manager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
PolarisPropertySourceManager.addPropertySource(polarisPropertySource);
PolarisRefreshAffectedContextRefresher refresher = context.getBean(PolarisRefreshAffectedContextRefresher.class);
PolarisRefreshAffectedContextRefresher spyRefresher = Mockito.spy(refresher);
refresher.setRegistered(false);
spyRefresher.onApplicationEvent(null);
ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED);
@ -134,12 +139,6 @@ public class PolarisConfigRefreshOptimizationListenerNotTriggeredTest {
@SpringBootApplication
protected static class TestApplication {
@Primary
@Bean
public PolarisPropertySourceManager polarisPropertySourceManager() {
return mock(PolarisPropertySourceManager.class);
}
@Primary
@Bean
public ContextRefresher contextRefresher() {

@ -21,7 +21,6 @@ package com.tencent.cloud.polaris.config.listener;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Lists;
import com.tencent.cloud.polaris.config.adapter.MockedConfigKVFile;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySource;
import com.tencent.cloud.polaris.config.adapter.PolarisPropertySourceManager;
@ -31,6 +30,7 @@ import com.tencent.cloud.polaris.config.enums.RefreshType;
import com.tencent.polaris.configuration.api.core.ChangeType;
import com.tencent.polaris.configuration.api.core.ConfigKVFileChangeEvent;
import com.tencent.polaris.configuration.api.core.ConfigPropertyChangeInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
@ -78,6 +78,11 @@ public class PolarisConfigRefreshOptimizationListenerTriggeredTest {
@Autowired
private ConfigurableApplicationContext context;
@BeforeEach
public void setUp() {
PolarisPropertySourceManager.clearPropertySources();
}
@Test
public void testSwitchConfigRefreshType() {
RefreshType actualRefreshType = context.getEnvironment()
@ -101,12 +106,12 @@ public class PolarisConfigRefreshOptimizationListenerTriggeredTest {
PolarisPropertySource polarisPropertySource = new PolarisPropertySource(TEST_NAMESPACE, TEST_SERVICE_NAME, TEST_FILE_NAME,
file, content);
PolarisPropertySourceManager manager = context.getBean(PolarisPropertySourceManager.class);
when(manager.getAllPropertySources()).thenReturn(Lists.newArrayList(polarisPropertySource));
PolarisPropertySourceManager.addPropertySource(polarisPropertySource);
PolarisRefreshEntireContextRefresher refresher = context.getBean(PolarisRefreshEntireContextRefresher.class);
PolarisRefreshEntireContextRefresher spyRefresher = Mockito.spy(refresher);
refresher.setRegistered(false);
spyRefresher.onApplicationEvent(null);
ConfigPropertyChangeInfo changeInfo = new ConfigPropertyChangeInfo("k1", "v1", "v11", ChangeType.MODIFIED);
@ -135,12 +140,6 @@ public class PolarisConfigRefreshOptimizationListenerTriggeredTest {
@SpringBootApplication
protected static class TestApplication {
@Primary
@Bean
public PolarisPropertySourceManager polarisPropertySourceManager() {
return mock(PolarisPropertySourceManager.class);
}
@Primary
@Bean
public ContextRefresher contextRefresher() {

@ -36,28 +36,13 @@
<!-- Spring cloud dependencies start -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>swagger-models</artifactId>
<groupId>io.swagger</groupId>
</exclusion>
<exclusion>
<artifactId>swagger-annotations</artifactId>
<groupId>io.swagger</groupId>
</exclusion>
</exclusions>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-webflux-ui</artifactId>
</dependency>
<dependency>

@ -17,27 +17,33 @@
package com.tencent.cloud.polaris.contract;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.cloud.common.util.JacksonUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencent.cloud.common.util.GzipUtil;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.contract.config.PolarisContractProperties;
import com.tencent.polaris.api.core.ProviderAPI;
import com.tencent.polaris.api.plugin.server.InterfaceDescriptor;
import com.tencent.polaris.api.plugin.server.ReportServiceContractRequest;
import com.tencent.polaris.api.plugin.server.ReportServiceContractResponse;
import io.swagger.models.HttpMethod;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import com.tencent.polaris.api.utils.StringUtils;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import springfox.documentation.service.Documentation;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper;
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.api.AbstractOpenApiResourceUtil;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.webflux.api.OpenApiWebFluxUtil;
import org.springdoc.webmvc.api.OpenApiWebMvcUtil;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
@ -52,50 +58,79 @@ import org.springframework.util.CollectionUtils;
public class PolarisContractReporter implements ApplicationListener<ApplicationReadyEvent> {
private final Logger LOG = LoggerFactory.getLogger(PolarisContractReporter.class);
private final ServiceModelToSwagger2Mapper swagger2Mapper;
private final DocumentationCache documentationCache;
private final org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource;
private final org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource;
private final PolarisContractProperties polarisContractProperties;
private final ProviderAPI providerAPI;
private final PolarisDiscoveryProperties polarisDiscoveryProperties;
public PolarisContractReporter(DocumentationCache documentationCache, ServiceModelToSwagger2Mapper swagger2Mapper,
PolarisContractProperties polarisContractProperties, ProviderAPI providerAPI, PolarisDiscoveryProperties polarisDiscoveryProperties) {
this.swagger2Mapper = swagger2Mapper;
this.documentationCache = documentationCache;
private final ObjectMapperProvider springdocObjectMapperProvider;
public PolarisContractReporter(org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource,
org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource,
PolarisContractProperties polarisContractProperties, ProviderAPI providerAPI,
PolarisDiscoveryProperties polarisDiscoveryProperties, ObjectMapperProvider springdocObjectMapperProvider) {
this.multipleOpenApiWebMvcResource = multipleOpenApiWebMvcResource;
this.multipleOpenApiWebFluxResource = multipleOpenApiWebFluxResource;
this.polarisContractProperties = polarisContractProperties;
this.providerAPI = providerAPI;
this.polarisDiscoveryProperties = polarisDiscoveryProperties;
this.springdocObjectMapperProvider = springdocObjectMapperProvider;
}
@Override
public void onApplicationEvent(@NonNull ApplicationReadyEvent applicationReadyEvent) {
if (polarisContractProperties.isReportEnabled()) {
try {
Documentation documentation = documentationCache.documentationByGroup(polarisContractProperties.getGroup());
Swagger swagger = swagger2Mapper.mapDocumentation(documentation);
if (swagger != null) {
AbstractOpenApiResource openApiResource = null;
if (multipleOpenApiWebMvcResource != null) {
openApiResource = OpenApiWebMvcUtil.getOpenApiResourceOrThrow(multipleOpenApiWebMvcResource, polarisContractProperties.getGroup());
}
else if (multipleOpenApiWebFluxResource != null) {
openApiResource = OpenApiWebFluxUtil.getOpenApiResourceOrThrow(multipleOpenApiWebFluxResource, polarisContractProperties.getGroup());
}
OpenAPI openAPI = null;
if (openApiResource != null) {
openAPI = AbstractOpenApiResourceUtil.getOpenApi(openApiResource);
}
if (openAPI != null) {
ReportServiceContractRequest request = new ReportServiceContractRequest();
request.setName(polarisDiscoveryProperties.getService());
String name = polarisContractProperties.getName();
if (StringUtils.isBlank(name)) {
name = polarisDiscoveryProperties.getService();
}
request.setName(name);
request.setNamespace(polarisDiscoveryProperties.getNamespace());
request.setService(polarisDiscoveryProperties.getService());
request.setProtocol("http");
request.setVersion(polarisDiscoveryProperties.getVersion());
List<InterfaceDescriptor> interfaceDescriptorList = getInterfaceDescriptorFromSwagger(swagger);
List<InterfaceDescriptor> interfaceDescriptorList = getInterfaceDescriptorFromSwagger(openAPI);
request.setInterfaceDescriptors(interfaceDescriptorList);
String jsonValue;
if (springdocObjectMapperProvider != null && springdocObjectMapperProvider.jsonMapper() != null) {
jsonValue = springdocObjectMapperProvider.jsonMapper().writeValueAsString(openAPI);
}
else {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jsonValue = mapper.writeValueAsString(openAPI);
}
String serviceApiMeta = GzipUtil.compressBase64Encode(jsonValue, "utf-8");
request.setContent(serviceApiMeta);
ReportServiceContractResponse response = providerAPI.reportServiceContract(request);
LOG.info("Service contract [Namespace: {}. Name: {}. Service: {}. Protocol:{}. Version: {}. API counter: {}] is reported.",
request.getNamespace(), request.getName(), request.getService(), request.getProtocol(),
request.getVersion(), request.getInterfaceDescriptors().size());
if (LOG.isDebugEnabled()) {
String jsonValue = JacksonUtils.serialize2Json(swagger);
LOG.debug("OpenApi json data: {}", jsonValue);
LOG.debug("OpenApi json base64 data: {}", serviceApiMeta);
}
}
else {
LOG.warn("Swagger or json is null, documentationCache keys:{}, group:{}", documentationCache.all()
.keySet(), polarisContractProperties.getGroup());
LOG.warn("OpenAPI or json is null, group:{}", polarisContractProperties.getGroup());
}
}
catch (Throwable t) {
@ -104,11 +139,11 @@ public class PolarisContractReporter implements ApplicationListener<ApplicationR
}
}
private List<InterfaceDescriptor> getInterfaceDescriptorFromSwagger(Swagger swagger) {
private List<InterfaceDescriptor> getInterfaceDescriptorFromSwagger(OpenAPI openAPI) {
List<InterfaceDescriptor> interfaceDescriptorList = new ArrayList<>();
Map<String, Path> paths = swagger.getPaths();
for (Map.Entry<String, Path> p : paths.entrySet()) {
Path path = p.getValue();
Paths paths = openAPI.getPaths();
for (Map.Entry<String, PathItem> p : paths.entrySet()) {
PathItem path = p.getValue();
Map<String, Operation> operationMap = getOperationMapFromPath(path);
if (CollectionUtils.isEmpty(operationMap)) {
continue;
@ -117,36 +152,50 @@ public class PolarisContractReporter implements ApplicationListener<ApplicationR
InterfaceDescriptor interfaceDescriptor = new InterfaceDescriptor();
interfaceDescriptor.setPath(p.getKey());
interfaceDescriptor.setMethod(o.getKey());
interfaceDescriptor.setContent(JacksonUtils.serialize2Json(p.getValue()));
try {
String jsonValue;
if (springdocObjectMapperProvider != null && springdocObjectMapperProvider.jsonMapper() != null) {
jsonValue = springdocObjectMapperProvider.jsonMapper().writeValueAsString(o.getValue());
}
else {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jsonValue = mapper.writeValueAsString(o.getValue());
}
interfaceDescriptor.setContent(GzipUtil.compressBase64Encode(jsonValue, "utf-8"));
}
catch (IOException ioe) {
LOG.warn("Encode operation [{}] failed.", o.getValue(), ioe);
}
interfaceDescriptorList.add(interfaceDescriptor);
}
}
return interfaceDescriptorList;
}
private Map<String, Operation> getOperationMapFromPath(Path path) {
private Map<String, Operation> getOperationMapFromPath(PathItem path) {
Map<String, Operation> operationMap = new HashMap<>();
if (path.getGet() != null) {
operationMap.put(HttpMethod.GET.name(), path.getGet());
operationMap.put(PathItem.HttpMethod.GET.name(), path.getGet());
}
if (path.getPut() != null) {
operationMap.put(HttpMethod.PUT.name(), path.getPut());
operationMap.put(PathItem.HttpMethod.PUT.name(), path.getPut());
}
if (path.getPost() != null) {
operationMap.put(HttpMethod.POST.name(), path.getPost());
operationMap.put(PathItem.HttpMethod.POST.name(), path.getPost());
}
if (path.getHead() != null) {
operationMap.put(HttpMethod.HEAD.name(), path.getHead());
operationMap.put(PathItem.HttpMethod.HEAD.name(), path.getHead());
}
if (path.getDelete() != null) {
operationMap.put(HttpMethod.DELETE.name(), path.getDelete());
operationMap.put(PathItem.HttpMethod.DELETE.name(), path.getDelete());
}
if (path.getPatch() != null) {
operationMap.put(HttpMethod.PATCH.name(), path.getPatch());
operationMap.put(PathItem.HttpMethod.PATCH.name(), path.getPatch());
}
if (path.getOptions() != null) {
operationMap.put(HttpMethod.OPTIONS.name(), path.getOptions());
operationMap.put(PathItem.HttpMethod.OPTIONS.name(), path.getOptions());
}
return operationMap;

@ -51,4 +51,8 @@ public interface ContractProperties {
boolean isReportEnabled();
void setReportEnabled(boolean reportEnabled);
String getName();
void setName(String name);
}

@ -41,7 +41,8 @@ public class PolarisContractModifier implements PolarisConfigModifier {
public void modify(ConfigurationImpl configuration) {
List<RegisterConfigImpl> registerConfigs = configuration.getProvider().getRegisters();
for (RegisterConfigImpl registerConfig : registerConfigs) {
registerConfig.setReportServiceContractEnable(polarisContractProperties.isEnabled());
registerConfig.setReportServiceContractEnable(
polarisContractProperties.isEnabled() && polarisContractProperties.isReportEnabled());
}
}

@ -17,10 +17,6 @@
package com.tencent.cloud.polaris.contract.config;
import java.util.Objects;
import javax.annotation.Nullable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -32,8 +28,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.cloud.polaris.contract")
public class PolarisContractProperties implements ContractProperties {
private final ExtendedContractProperties extendContractProperties;
private boolean enabled = true;
/**
* Packages to be scanned. Split by ",".
@ -46,7 +40,7 @@ public class PolarisContractProperties implements ContractProperties {
/**
* Group to create swagger docket.
*/
private String group = "default";
private String group = "polaris";
/**
* Base paths to be scanned. Split by ",".
*/
@ -57,15 +51,10 @@ public class PolarisContractProperties implements ContractProperties {
@Value("${spring.cloud.polaris.contract.report.enabled:true}")
private boolean reportEnabled = true;
public PolarisContractProperties(@Nullable ExtendedContractProperties extendContractProperties) {
this.extendContractProperties = extendContractProperties;
}
private String name;
@Override
public boolean isEnabled() {
if (Objects.nonNull(extendContractProperties)) {
return extendContractProperties.isEnabled();
}
return enabled;
}
@ -76,9 +65,6 @@ public class PolarisContractProperties implements ContractProperties {
@Override
public String getBasePackage() {
if (Objects.nonNull(extendContractProperties)) {
return extendContractProperties.getBasePackage();
}
return basePackage;
}
@ -89,9 +75,6 @@ public class PolarisContractProperties implements ContractProperties {
@Override
public String getExcludePath() {
if (Objects.nonNull(extendContractProperties)) {
return extendContractProperties.getExcludePath();
}
return excludePath;
}
@ -102,9 +85,6 @@ public class PolarisContractProperties implements ContractProperties {
@Override
public String getGroup() {
if (Objects.nonNull(extendContractProperties)) {
return extendContractProperties.getGroup();
}
return group;
}
@ -115,9 +95,6 @@ public class PolarisContractProperties implements ContractProperties {
@Override
public String getBasePath() {
if (Objects.nonNull(extendContractProperties)) {
return extendContractProperties.getBasePath();
}
return basePath;
}
@ -128,9 +105,6 @@ public class PolarisContractProperties implements ContractProperties {
@Override
public boolean isExposure() {
if (Objects.nonNull(extendContractProperties)) {
return extendContractProperties.isExposure();
}
return exposure;
}
@ -148,4 +122,13 @@ public class PolarisContractProperties implements ContractProperties {
public void setReportEnabled(boolean reportEnabled) {
this.reportEnabled = reportEnabled;
}
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}

@ -17,8 +17,6 @@
package com.tencent.cloud.polaris.contract.config;
import javax.annotation.Nullable;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -36,8 +34,8 @@ public class PolarisContractPropertiesAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public PolarisContractProperties polarisContractProperties(@Nullable ExtendedContractProperties extendedContractProperties) {
return new PolarisContractProperties(extendedContractProperties);
public PolarisContractProperties polarisContractProperties() {
return new PolarisContractProperties();
}
@Bean

@ -17,11 +17,6 @@
package com.tencent.cloud.polaris.contract.config;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
import com.tencent.cloud.polaris.PolarisDiscoveryProperties;
import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled;
import com.tencent.cloud.polaris.context.PolarisSDKContextManager;
@ -30,13 +25,14 @@ import com.tencent.cloud.polaris.contract.PolarisSwaggerApplicationListener;
import com.tencent.cloud.polaris.contract.filter.ApiDocServletFilter;
import com.tencent.cloud.polaris.contract.filter.ApiDocWebFluxFilter;
import com.tencent.cloud.polaris.contract.utils.PackageUtil;
import springfox.boot.starter.autoconfigure.OpenApiAutoConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.SpringDocConfiguration;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.webflux.api.MultipleOpenApiWebFluxResource;
import org.springdoc.webmvc.api.MultipleOpenApiWebMvcResource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -45,6 +41,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import static com.tencent.cloud.polaris.contract.utils.PackageUtil.SPLITTER;
/**
* Auto configuration for Polaris swagger.
@ -54,7 +54,7 @@ import org.springframework.context.annotation.Import;
@Configuration(proxyBeanMethods = false)
@ConditionalOnPolarisEnabled
@ConditionalOnProperty(name = "spring.cloud.polaris.contract.enabled", havingValue = "true", matchIfMissing = true)
@Import(OpenApiAutoConfiguration.class)
@Import(SpringDocConfiguration.class)
public class PolarisSwaggerAutoConfiguration {
static {
@ -64,65 +64,45 @@ public class PolarisSwaggerAutoConfiguration {
}
@Bean
public Docket polarisDocket(PolarisContractProperties polarisContractProperties) {
List<Predicate<String>> excludePathList = PackageUtil.getExcludePathPredicates(polarisContractProperties.getExcludePath());
List<Predicate<String>> basePathList = PackageUtil.getBasePathPredicates(polarisContractProperties.getBasePath());
public GroupedOpenApi polarisGroupedOpenApi(PolarisContractProperties polarisContractProperties) {
String basePackage = PackageUtil.scanPackage(polarisContractProperties.getBasePackage());
Predicate<String> basePathListOr = null;
for (Predicate<String> basePathPredicate : basePathList) {
if (basePathListOr == null) {
basePathListOr = basePathPredicate;
}
else {
basePathListOr = basePathListOr.or(basePathPredicate);
}
}
Predicate<String> excludePathListOr = null;
for (Predicate<String> excludePathPredicate : excludePathList) {
if (excludePathListOr == null) {
excludePathListOr = excludePathPredicate;
}
else {
excludePathListOr = excludePathListOr.or(excludePathPredicate);
}
String[] basePaths = {};
if (StringUtils.hasText(polarisContractProperties.getBasePath())) {
basePaths = polarisContractProperties.getBasePath().split(SPLITTER);
}
Predicate<String> pathsPredicate = basePathListOr;
if (excludePathListOr != null) {
excludePathListOr = excludePathListOr.negate();
pathsPredicate = pathsPredicate.and(excludePathListOr);
String[] excludePaths = {};
if (StringUtils.hasText(polarisContractProperties.getExcludePath())) {
excludePaths = polarisContractProperties.getExcludePath().split(SPLITTER);
}
return GroupedOpenApi.builder()
.packagesToScan(basePackage)
.pathsToMatch(basePaths)
.pathsToExclude(excludePaths)
.group(polarisContractProperties.getGroup())
.build();
}
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(PackageUtil.basePackage(basePackage))
.paths(pathsPredicate)
.build()
.groupName(polarisContractProperties.getGroup())
.enable(polarisContractProperties.isEnabled())
.directModelSubstitute(LocalDate.class, Date.class)
.apiInfo(new ApiInfoBuilder()
.title("Polaris Swagger API")
.description("This is to show polaris api description.")
.license("BSD-3-Clause")
.licenseUrl("https://opensource.org/licenses/BSD-3-Clause")
.termsOfServiceUrl("")
.version("1.0.0")
.contact(new Contact("", "", ""))
.build());
@Bean
@ConditionalOnMissingBean
public OpenAPI polarisOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Polaris Contract")
.description("This is to show polaris contract description.")
.license(new License().name("BSD-3-Clause").url("https://opensource.org/licenses/BSD-3-Clause"))
.version("1.0.0"));
}
@Bean
@ConditionalOnBean(Docket.class)
@ConditionalOnBean(OpenAPI.class)
@ConditionalOnMissingBean
public PolarisContractReporter polarisContractReporter(DocumentationCache documentationCache,
ServiceModelToSwagger2Mapper swagger2Mapper, PolarisContractProperties polarisContractProperties,
PolarisSDKContextManager polarisSDKContextManager, PolarisDiscoveryProperties polarisDiscoveryProperties) {
return new PolarisContractReporter(documentationCache, swagger2Mapper, polarisContractProperties,
polarisSDKContextManager.getProviderAPI(), polarisDiscoveryProperties);
public PolarisContractReporter polarisContractReporter(
@Nullable MultipleOpenApiWebMvcResource multipleOpenApiWebMvcResource,
@Nullable MultipleOpenApiWebFluxResource multipleOpenApiWebFluxResource,
PolarisContractProperties polarisContractProperties, PolarisSDKContextManager polarisSDKContextManager,
PolarisDiscoveryProperties polarisDiscoveryProperties, ObjectMapperProvider springdocObjectMapperProvider) {
return new PolarisContractReporter(multipleOpenApiWebMvcResource, multipleOpenApiWebFluxResource,
polarisContractProperties, polarisSDKContextManager.getProviderAPI(), polarisDiscoveryProperties, springdocObjectMapperProvider);
}
@Bean

@ -26,7 +26,6 @@ import javax.servlet.http.HttpServletResponse;
import com.tencent.cloud.polaris.contract.config.PolarisContractProperties;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;
import static com.tencent.cloud.polaris.contract.filter.FilterConstant.SWAGGER_RESOURCE_PREFIX;
@ -51,11 +50,9 @@ public class ApiDocServletFilter extends OncePerRequestFilter {
}
@Override
public void doFilterInternal(@NonNull HttpServletRequest httpServletRequest,
@NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain)
throws ServletException, IOException {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!polarisContractProperties.isExposure()) {
String path = httpServletRequest.getServletPath();
String path = request.getServletPath();
if (path.startsWith(SWAGGER_V2_API_DOC_URL) ||
path.startsWith(SWAGGER_V3_API_DOC_URL) ||
path.startsWith(SWAGGER_UI_V2_URL) ||
@ -63,11 +60,11 @@ public class ApiDocServletFilter extends OncePerRequestFilter {
path.startsWith(SWAGGER_RESOURCE_PREFIX) ||
path.startsWith(SWAGGER_WEBJARS_V2_PREFIX) ||
path.startsWith(SWAGGER_WEBJARS_V3_PREFIX)) {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
filterChain.doFilter(request, response);
}
}

@ -17,13 +17,13 @@
package com.tencent.cloud.polaris.contract.filter;
import com.tencent.cloud.polaris.contract.config.PolarisContractProperties;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
@ -42,6 +42,7 @@ import static com.tencent.cloud.polaris.contract.filter.FilterConstant.SWAGGER_W
* @author Haotian Zhang
*/
public class ApiDocWebFluxFilter implements WebFilter {
private final PolarisContractProperties polarisContractProperties;
public ApiDocWebFluxFilter(PolarisContractProperties polarisContractProperties) {
@ -49,7 +50,7 @@ public class ApiDocWebFluxFilter implements WebFilter {
}
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
public Mono<Void> filter(ServerWebExchange serverWebExchange, @NonNull WebFilterChain webFilterChain) {
if (!polarisContractProperties.isExposure()) {
String path = serverWebExchange.getRequest().getURI().getPath();
if (path.startsWith(SWAGGER_V2_API_DOC_URL) ||

@ -0,0 +1,137 @@
/*
* 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.contract.tsf;
import java.util.concurrent.atomic.AtomicBoolean;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencent.cloud.common.util.GzipUtil;
import io.swagger.v3.oas.models.OpenAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springdoc.api.AbstractOpenApiResource;
import org.springdoc.api.AbstractOpenApiResourceUtil;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.webflux.api.OpenApiWebFluxUtil;
import org.springdoc.webmvc.api.OpenApiWebMvcUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
public class TsfApiMetadataGrapher implements SmartLifecycle {
private final AtomicBoolean isRunning = new AtomicBoolean(false);
private final org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource;
private final org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource;
private final ObjectMapperProvider springdocObjectMapperProvider;
private Logger logger = LoggerFactory.getLogger(TsfApiMetadataGrapher.class);
private ApplicationContext applicationContext;
private String groupName;
public TsfApiMetadataGrapher(org.springdoc.webmvc.api.MultipleOpenApiResource multipleOpenApiWebMvcResource,
org.springdoc.webflux.api.MultipleOpenApiResource multipleOpenApiWebFluxResource,
String groupName, ApplicationContext applicationContext, ObjectMapperProvider springdocObjectMapperProvider) {
this.applicationContext = applicationContext;
this.multipleOpenApiWebMvcResource = multipleOpenApiWebMvcResource;
this.multipleOpenApiWebFluxResource = multipleOpenApiWebFluxResource;
this.groupName = groupName;
this.springdocObjectMapperProvider = springdocObjectMapperProvider;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable runnable) {
runnable.run();
stop();
}
@Override
public void start() {
if (!isRunning.compareAndSet(false, true)) {
return;
}
try {
AbstractOpenApiResource openApiResource = null;
if (multipleOpenApiWebMvcResource != null) {
openApiResource = OpenApiWebMvcUtil.getOpenApiResourceOrThrow(multipleOpenApiWebMvcResource, groupName);
}
else if (multipleOpenApiWebFluxResource != null) {
openApiResource = OpenApiWebFluxUtil.getOpenApiResourceOrThrow(multipleOpenApiWebFluxResource, groupName);
}
OpenAPI openAPI = null;
if (openApiResource != null) {
openAPI = AbstractOpenApiResourceUtil.getOpenApi(openApiResource);
}
String jsonValue;
if (springdocObjectMapperProvider != null && springdocObjectMapperProvider.jsonMapper() != null) {
jsonValue = springdocObjectMapperProvider.jsonMapper().writeValueAsString(openAPI);
}
else {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jsonValue = mapper.writeValueAsString(openAPI);
}
if (openAPI != null && !StringUtils.isEmpty(jsonValue)) {
String serviceApiMeta = GzipUtil.compressBase64Encode(jsonValue, "utf-8");
Environment environment = applicationContext.getEnvironment();
String tsfToken = environment.getProperty("tsf_token");
String tsfGroupId = environment.getProperty("tsf_group_id");
if (StringUtils.isEmpty(tsfGroupId) || StringUtils.isEmpty(tsfToken)) {
logger.info("[tsf-swagger] auto smart check application start with local consul, api registry not work");
return;
}
logger.info("[tsf-swagger] api_meta len: {}", serviceApiMeta.length());
String applicationName = environment.getProperty("spring.application.name");
if (logger.isDebugEnabled()) {
logger.debug("[tsf-swagger] service: {} openApi json data: {}", applicationName, jsonValue);
logger.debug("[tsf-swagger] service: {} api_meta info: {}", applicationName, serviceApiMeta);
}
System.setProperty(String.format("$%s", "api_metas"), serviceApiMeta);
}
else {
logger.warn("[tsf-swagger] swagger or json is null, openApiResource keys:{}, group:{}", openApiResource, groupName);
}
}
catch (Throwable t) {
logger.error("[tsf swagger] init TsfApiMetadataGrapher failed. occur exception: ", t);
}
}
@Override
public void stop() {
isRunning.set(true);
}
@Override
public boolean isRunning() {
return isRunning.get();
}
@Override
public int getPhase() {
return -2;
}
}

@ -0,0 +1,46 @@
/*
* 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.contract.tsf;
import com.tencent.cloud.common.tsf.ConditionalOnTsfConsulEnabled;
import com.tencent.cloud.polaris.contract.config.PolarisContractProperties;
import io.swagger.v3.oas.models.OpenAPI;
import org.springdoc.core.providers.ObjectMapperProvider;
import org.springdoc.webflux.api.MultipleOpenApiWebFluxResource;
import org.springdoc.webmvc.api.MultipleOpenApiWebMvcResource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
@Configuration
@ConditionalOnTsfConsulEnabled
@ConditionalOnProperty(value = "tsf.swagger.enabled", havingValue = "true", matchIfMissing = true)
public class TsfSwaggerAutoConfiguration {
@Bean
@ConditionalOnBean(OpenAPI.class)
public TsfApiMetadataGrapher tsfApiMetadataGrapher(@Nullable MultipleOpenApiWebMvcResource multipleOpenApiWebMvcResource,
@Nullable MultipleOpenApiWebFluxResource multipleOpenApiWebFluxResource, ApplicationContext context,
PolarisContractProperties polarisContractProperties, ObjectMapperProvider springdocObjectMapperProvider) {
return new TsfApiMetadataGrapher(multipleOpenApiWebMvcResource, multipleOpenApiWebFluxResource,
polarisContractProperties.getGroup(), context, springdocObjectMapperProvider);
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save