Merge branch 'apache-3.2' into apache-3.3

# Conflicts:
#	dubbo-dependencies-bom/pom.xml
#	dubbo-dependencies/dubbo-dependencies-zookeeper-curator5/pom.xml
#	dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml
#	pom.xml
This commit is contained in:
Albumen Kevin 2023-03-21 17:49:14 +08:00
commit 414785c05b
886 changed files with 26277 additions and 4749 deletions

View File

@ -24,7 +24,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Check License
uses: apache/skywalking-eyes@main
uses: apache/skywalking-eyes@e1a02359b239bd28de3f6d35fdc870250fa513d5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Set up JDK 17"
@ -36,7 +36,7 @@ jobs:
run: |
./mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean install -DskipTests=true -DskipIntegrationTests=true -Dcheckstyle.skip=true -Dcheckstyle_unix.skip=true -Drat.skip=true -Dmaven.javadoc.skip=true
- name: Check Dependencies' License
uses: apache/skywalking-eyes/dependency@main
uses: apache/skywalking-eyes/dependency@e1a02359b239bd28de3f6d35fdc870250fa513d5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@ -79,15 +79,6 @@ jobs:
with:
name: "class-file"
path: ${{ github.workspace }}/class.zip
- name: "Pack rat file if failure"
if: failure()
run: 7z a ${{ github.workspace }}/rat.zip *rat.txt -r
- name: "Upload rat file if failure"
if: failure()
uses: actions/upload-artifact@v3
with:
name: "rat-file"
path: ${{ github.workspace }}/rat.zip
- name: "Pack checkstyle file if failure"
if: failure()
run: 7z a ${{ github.workspace }}/checkstyle.zip *checkstyle* -r
@ -440,8 +431,8 @@ jobs:
- name: "Compile and run Dubbo demo for native (Linux)"
run: |
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
nohup ./target/demo-native-provider &
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
nohup ./target/dubbo-demo-native-provider &
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
./target/demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
./target/dubbo-demo-native-consumer

View File

@ -395,8 +395,8 @@ jobs:
- name: "Compile and run Dubbo demo for native (Linux)"
run: |
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
nohup ./target/demo-native-provider &
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
nohup ./target/dubbo-demo-native-provider &
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
./target/demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
./target/dubbo-demo-native-consumer

View File

@ -395,8 +395,8 @@ jobs:
- name: "Compile and run Dubbo demo for native (Linux)"
run: |
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
nohup ./target/demo-native-provider &
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
nohup ./target/dubbo-demo-native-provider &
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
./target/demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
./target/dubbo-demo-native-consumer

View File

@ -374,8 +374,8 @@ jobs:
- name: "Compile and run Dubbo demo for native (Linux)"
run: |
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
nohup ./target/demo-native-provider &
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
nohup ./target/dubbo-demo-native-provider &
cd ${{ github.workspace }}/dubbo/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true
./target/demo-native-consumer
${{ github.workspace }}/dubbo/mvnw --batch-mode --no-snapshot-updates -e --no-transfer-progress --fail-fast -T 2C clean package -P native -Dmaven.test.skip=true native:compile
./target/dubbo-demo-native-consumer

5
.gitignore vendored
View File

@ -48,8 +48,5 @@ dubbo-demo/dubbo-demo-triple/build/*
# global registry center
.tmp
dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/src/main/resources/META-INF/native-image
dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/src/main/generated
dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/src/main/resources/META-INF/native-image
dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/src/main/generated
.git.exec.error

View File

@ -8,9 +8,9 @@ header:
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
@ -67,6 +67,7 @@ header:
- 'CNAME'
- 'Jenkinsfile'
- '**/vendor/**'
- '**/src/test/resources/certs/**'
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalThreadLocal.java'
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadlocal/InternalThreadLocalMap.java'
- 'dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java'
@ -79,6 +80,24 @@ header:
- 'dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/utils/TestSocketUtils.java'
- 'dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriHttp2RemoteFlowController.java'
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/serial/SerializingExecutor.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractAotMojo.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractDependencyFilterMojo.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/CommandLineBuilder.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/DependencyFilter.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/Exclude.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/ExcludeFilter.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/FilterableDependency.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/Include.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/IncludeFilter.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/JavaCompilerPluginConfiguration.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/JavaExecutable.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/JavaProcessExecutor.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/MatchingGroupIdFilter.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/RunArguments.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/RunProcess.java'
- 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/BasicJsonWriter.java'
- 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/ExecutableMode.java'
- 'dubbo-native/src/main/java/org/apache/dubbo/aot/generate/MemberCategory.java'
comment: on-failure
@ -171,6 +190,16 @@ dependency:
license: Apache-2.0
- name: org.slf4j:jul-to-slf4j
license: Apache-2.0
- name: org.codehaus.plexus:plexus-interpolation
license: Apache-2.0
- name: org.sonatype.plexus:plexus-sec-dispatcher
license: Apache-2.0
- name: org.sonatype.plexus:plexus-cipher
license: Apache-2.0
- name: com.google.protobuf:protobuf-java
license: BSD 3-clause
- name: com.google.protobuf:protobuf-java-util
license: BSD 3-clause
# multi license
- name: org.javassist:javassist
license: Apache-2.0
@ -196,5 +225,9 @@ dependency:
license: EPL-2.0
- name: org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec
license: CDDL-1.1
- name: org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec
license: EPL-2.0
- name: org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec
license: EPL-2.0
excludes:
- name: javax.xml.bind:jsr173_api

View File

@ -1,7 +1,7 @@
# Apache Dubbo Project
[![Build Status](https://github.com/apache/dubbo/workflows/Build%20and%20Test%20For%20Dubbo%203/badge.svg?branch=3.0)](https://github.com/apache/dubbo/actions/workflows/build-and-test-3.yml?query=branch%3A3.0)
[![Codecov](https://codecov.io/gh/apache/dubbo/branch/3.0/graph/badge.svg)](https://codecov.io/gh/apache/dubbo)
[![Build and Test For PR](https://github.com/apache/dubbo/actions/workflows/build-and-test-pr.yml/badge.svg)](https://github.com/apache/dubbo/actions/workflows/build-and-test-pr.yml)
[![Codecov](https://codecov.io/gh/apache/dubbo/branch/3.2/graph/badge.svg)](https://codecov.io/gh/apache/dubbo)
![Maven](https://img.shields.io/maven-central/v/org.apache.dubbo/dubbo.svg)
![License](https://img.shields.io/github/license/alibaba/dubbo.svg)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/dubbo.svg)](http://isitmaintained.com/project/apache/dubbo "Average time to resolve an issue")
@ -29,14 +29,14 @@ We are now collecting Dubbo user info to help us to improve Dubbo further. Kindl
## Getting started
The following code snippet comes from [Dubbo Samples](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-api). You may clone the sample project and step into the `dubbo-samples-api` subdirectory before proceeding.
The following code snippet comes from [Dubbo Samples](https://github.com/apache/dubbo-samples.git). You may clone the sample project and step into the `dubbo-samples-api` subdirectory before proceeding.
```bash
git clone https://github.com/apache/dubbo-samples.git
cd dubbo-samples/dubbo-samples-api
```
There's a [README](https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-api/README.md) file under `dubbo-samples-api` directory. We recommend referencing the samples in that directory by following the below-mentioned instructions:
There's a [README](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/README.md) file under `dubbo-samples-api` directory. We recommend referencing the samples in that directory by following the below-mentioned instructions:
### Maven dependency
@ -70,7 +70,7 @@ public interface GreetingsService {
}
```
*See [api/GreetingsService.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/api/GreetingsService.java) on GitHub.*
*See [api/GreetingsService.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/api/GreetingsService.java) on GitHub.*
### Implement service interface for the provider
@ -87,7 +87,7 @@ public class GreetingsServiceImpl implements GreetingsService {
}
```
*See [provider/GreetingsServiceImpl.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/provider/GreetingsServiceImpl.java) on GitHub.*
*See [provider/GreetingsServiceImpl.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/provider/GreetingsServiceImpl.java) on GitHub.*
### Start service provider
@ -119,7 +119,7 @@ public class Application {
}
```
*See [provider/Application.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/provider/Application.java) on GitHub.*
*See [provider/Application.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-spring-xml/src/main/java/org/apache/dubbo/samples/provider/Application.java) on GitHub.*
### Build and run the provider
@ -153,7 +153,7 @@ public class Application {
}
}
```
*See [consumer/Application.java](https://github.com/apache/dubbo-samples/blob/master/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/client/Application.java) on GitHub.*
*See [client/Application.java](https://github.com/apache/dubbo-samples/blob/389cd612f1ea57ee6e575005b32f195c442c35a2/1-basic/dubbo-samples-api/src/main/java/org/apache/dubbo/samples/client/Application.java) on GitHub.*
### Build and run the consumer

View File

@ -5,5 +5,6 @@
<suppressions>
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]com[\\/]alibaba[\\/]com[\\/]caucho[\\/]hessian" checks=".*"/>
<suppress files="[\\/]build[\\/]generated[\\/]source[\\/]proto" checks=".*"/>
<suppress files="[\\/]target[\\/]generated-sources[\\/]protobuf" checks=".*"/>
<suppress files="Yylex\.java" checks="AvoidEscapedUnicodeCharacters"/>
</suppressions>
</suppressions>

View File

@ -40,6 +40,12 @@
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-injvm</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
@ -68,5 +74,11 @@
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metrics-registry</artifactId>
<version>${project.parent.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.cluster;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@ -33,7 +34,7 @@ public interface ConfiguratorFactory {
* @param url - configurator url.
* @return configurator instance.
*/
@Adaptive("protocol")
@Adaptive(CommonConstants.PROTOCOL_KEY)
Configurator getConfigurator(URL url);
}

View File

@ -72,8 +72,6 @@ public interface Constants {
*/
boolean DEFAULT_CLUSTER_STICKY = false;
String ADDRESS_KEY = "address";
/**
* When this attribute appears in invocation's attachment, mock invoker will be used
*/
@ -137,4 +135,8 @@ public interface Constants {
String SHORTEST_RESPONSE_SLIDE_PERIOD = "shortestResponseSlidePeriod";
String SHOULD_FAIL_FAST_KEY = "dubbo.router.should-fail-fast";
String RULE_VERSION_V27 = "v2.7";
String RULE_VERSION_V30 = "v3.0";
}

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.cluster;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@ -42,6 +43,6 @@ public interface RouterFactory {
* @param url url
* @return router instance
*/
@Adaptive("protocol")
@Adaptive(CommonConstants.PROTOCOL_KEY)
Router getRouter(URL url);
}

View File

@ -17,10 +17,14 @@
package org.apache.dubbo.rpc.cluster.configurator;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.rpc.cluster.Configurator;
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConditionMatch;
import java.util.HashSet;
import java.util.Map;
@ -41,12 +45,14 @@ import static org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CON
import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.CONFIG_VERSION_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V30;
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
/**
* AbstractConfigurator
*/
public abstract class AbstractConfigurator implements Configurator {
private static final Logger logger = LoggerFactory.getLogger(AbstractConfigurator.class);
private static final String TILDE = "~";
private final URL configuratorUrl;
@ -67,19 +73,17 @@ public abstract class AbstractConfigurator implements Configurator {
public URL configure(URL url) {
// If override url is not enabled or is invalid, just return.
if (!configuratorUrl.getParameter(ENABLED_KEY, true) || configuratorUrl.getHost() == null || url == null || url.getHost() == null) {
logger.info("Cannot apply configurator rule, the rule is disabled or is invalid: \n" + configuratorUrl);
return url;
}
/*
* This if branch is created since 2.7.0.
*/
String apiVersion = configuratorUrl.getParameter(CONFIG_VERSION_KEY);
if (StringUtils.isNotEmpty(apiVersion)) {
if (StringUtils.isNotEmpty(apiVersion)) { // v2.7 or above
String currentSide = url.getSide();
String configuratorSide = configuratorUrl.getSide();
if (currentSide.equals(configuratorSide) && CONSUMER.equals(configuratorSide) && 0 == configuratorUrl.getPort()) {
if (currentSide.equals(configuratorSide) && CONSUMER.equals(configuratorSide)) {
url = configureIfMatch(NetUtils.getLocalHost(), url);
} else if (currentSide.equals(configuratorSide) && PROVIDER.equals(configuratorSide) &&
url.getPort() == configuratorUrl.getPort()) {
} else if (currentSide.equals(configuratorSide) && PROVIDER.equals(configuratorSide)) {
url = configureIfMatch(url.getHost(), url);
}
}
@ -118,51 +122,114 @@ public abstract class AbstractConfigurator implements Configurator {
private URL configureIfMatch(String host, URL url) {
if (ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) {
// TODO, to support wildcards
String providers = configuratorUrl.getParameter(OVERRIDE_PROVIDERS_KEY);
if (StringUtils.isEmpty(providers) || providers.contains(url.getAddress()) || providers.contains(ANYHOST_VALUE)) {
String configApplication = configuratorUrl.getApplication(configuratorUrl.getUsername());
String currentApplication = url.getApplication(url.getUsername());
if (configApplication == null || ANY_VALUE.equals(configApplication)
|| configApplication.equals(currentApplication)) {
Set<String> conditionKeys = new HashSet<String>();
conditionKeys.add(CATEGORY_KEY);
conditionKeys.add(Constants.CHECK_KEY);
conditionKeys.add(DYNAMIC_KEY);
conditionKeys.add(ENABLED_KEY);
conditionKeys.add(GROUP_KEY);
conditionKeys.add(VERSION_KEY);
conditionKeys.add(APPLICATION_KEY);
conditionKeys.add(SIDE_KEY);
conditionKeys.add(CONFIG_VERSION_KEY);
conditionKeys.add(COMPATIBLE_CONFIG_KEY);
conditionKeys.add(INTERFACES);
for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
boolean startWithTilde = startWithTilde(key);
if (startWithTilde || APPLICATION_KEY.equals(key) || SIDE_KEY.equals(key)) {
if (startWithTilde) {
conditionKeys.add(key);
}
if (value != null && !ANY_VALUE.equals(value)
&& !value.equals(url.getParameter(startWithTilde ? key.substring(1) : key))) {
return url;
}
if (isV27ConditionMatchOrUnset(url)) {
Set<String> conditionKeys = genConditionKeys();
String apiVersion = configuratorUrl.getParameter(CONFIG_VERSION_KEY);
if (apiVersion != null && apiVersion.startsWith(RULE_VERSION_V30)) {
ConditionMatch matcher = (ConditionMatch) configuratorUrl.getAttribute(MATCH_CONDITION);
if (matcher != null) {
if (matcher.isMatch(url)) {
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
} else {
logger.debug("Cannot apply configurator rule, param mismatch, current params are " + url + ", params in rule is " + matcher);
}
} else {
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
}
} else if (isDeprecatedConditionMatch(conditionKeys, url)) {
return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
}
}
} else {
logger.debug("Cannot apply configurator rule, host mismatch, current host is " + host + ", host in rule is " + configuratorUrl.getHost());
}
return url;
}
private boolean startWithTilde(String key) {
if (StringUtils.isNotEmpty(key) && key.startsWith(TILDE)) {
return true;
/**
* Check if v2.7 configurator rule is set and can be matched.
*
* @param url the configurator rule url
* @return true if v2.7 configurator rule is not set or the rule can be matched.
*/
private boolean isV27ConditionMatchOrUnset(URL url) {
String providers = configuratorUrl.getParameter(OVERRIDE_PROVIDERS_KEY);
if (StringUtils.isNotEmpty(providers)) {
boolean match = false;
String[] providerAddresses = providers.split(CommonConstants.COMMA_SEPARATOR);
for (String address : providerAddresses) {
if (address.equals(url.getAddress())
|| address.equals(ANYHOST_VALUE)
|| address.equals(ANYHOST_VALUE + CommonConstants.GROUP_CHAR_SEPARATOR + ANY_VALUE)
|| address.equals(ANYHOST_VALUE + CommonConstants.GROUP_CHAR_SEPARATOR + url.getPort())
|| address.equals(url.getHost())) {
match = true;
}
}
if (!match) {
logger.debug("Cannot apply configurator rule, provider address mismatch, current address " + url.getAddress() + ", address in rule is " + providers);
return false;
}
}
return false;
String configApplication = configuratorUrl.getApplication(configuratorUrl.getUsername());
String currentApplication = url.getApplication(url.getUsername());
if (configApplication != null
&& !ANY_VALUE.equals(configApplication)
&& !configApplication.equals(currentApplication)) {
logger.debug("Cannot apply configurator rule, application name mismatch, current application is " + currentApplication + ", application in rule is " + configApplication);
return false;
}
String configServiceKey = configuratorUrl.getServiceKey();
String currentServiceKey = url.getServiceKey();
if (!ANY_VALUE.equals(configServiceKey)
&& !configServiceKey.equals(currentServiceKey)) {
logger.debug("Cannot apply configurator rule, service mismatch, current service is " + currentServiceKey + ", service in rule is " + configServiceKey);
return false;
}
return true;
}
private boolean isDeprecatedConditionMatch(Set<String> conditionKeys, URL url) {
boolean result = true;
for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
boolean startWithTilde = startWithTilde(key);
if (startWithTilde || APPLICATION_KEY.equals(key) || SIDE_KEY.equals(key)) {
if (startWithTilde) {
conditionKeys.add(key);
}
if (value != null && !ANY_VALUE.equals(value)
&& !value.equals(url.getParameter(startWithTilde ? key.substring(1) : key))) {
result = false;
break;
}
}
}
return result;
}
private Set<String> genConditionKeys() {
Set<String> conditionKeys = new HashSet<String>();
conditionKeys.add(CATEGORY_KEY);
conditionKeys.add(Constants.CHECK_KEY);
conditionKeys.add(DYNAMIC_KEY);
conditionKeys.add(ENABLED_KEY);
conditionKeys.add(GROUP_KEY);
conditionKeys.add(VERSION_KEY);
conditionKeys.add(APPLICATION_KEY);
conditionKeys.add(SIDE_KEY);
conditionKeys.add(CONFIG_VERSION_KEY);
conditionKeys.add(COMPATIBLE_CONFIG_KEY);
conditionKeys.add(INTERFACES);
return conditionKeys;
}
private boolean startWithTilde(String key) {
return StringUtils.isNotEmpty(key) && key.startsWith(TILDE);
}
protected abstract URL doConfigure(URL currentUrl, URL configUrl);

View File

@ -17,12 +17,15 @@
package org.apache.dubbo.rpc.cluster.configurator.override;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.rpc.cluster.configurator.AbstractConfigurator;
/**
* OverrideConfigurator
*/
public class OverrideConfigurator extends AbstractConfigurator {
public static final Logger logger = LoggerFactory.getLogger(OverrideConfigurator.class);
public OverrideConfigurator(URL url) {
super(url);
@ -30,6 +33,7 @@ public class OverrideConfigurator extends AbstractConfigurator {
@Override
public URL doConfigure(URL currentUrl, URL configUrl) {
logger.info("Start overriding url " + currentUrl + " with override url " + configUrl);
return currentUrl.addParameters(configUrl.getParameters());
}

View File

@ -31,9 +31,11 @@ import java.util.List;
import java.util.Map;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
import static org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY;
import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_CONFIGURATORS_CATEGORY;
import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
/**
* Config parser
@ -59,6 +61,7 @@ public class ConfigParser {
// service scope by default.
items.forEach(item -> urls.addAll(serviceItemToUrls(item, configuratorConfig)));
}
return urls;
}
@ -100,10 +103,10 @@ public class ConfigParser {
if (CollectionUtils.isNotEmpty(apps)) {
apps.forEach(app -> {
StringBuilder tmpUrlBuilder = new StringBuilder(urlBuilder);
urls.add(URL.valueOf(tmpUrlBuilder.append("&application=").append(app).toString()));
urls.add(appendMatchCondition(URL.valueOf(tmpUrlBuilder.append("&application=").append(app).toString()), item));
});
} else {
urls.add(URL.valueOf(urlBuilder.toString()));
urls.add(appendMatchCondition(URL.valueOf(urlBuilder.toString()), item));
}
});
@ -135,7 +138,7 @@ public class ConfigParser {
tmpUrlBuilder.append("&category=").append(APP_DYNAMIC_CONFIGURATORS_CATEGORY);
tmpUrlBuilder.append("&configVersion=").append(config.getConfigVersion());
urls.add(URL.valueOf(tmpUrlBuilder.toString()));
urls.add(appendMatchCondition(URL.valueOf(tmpUrlBuilder.toString()), item));
}
}
return urls;
@ -167,6 +170,11 @@ public class ConfigParser {
sb.append(OVERRIDE_PROVIDERS_KEY);
sb.append('=');
sb.append(CollectionUtils.join(item.getProviderAddresses(), ","));
} else if (PROVIDER.equals(item.getSide())) {
sb.append('&');
sb.append(OVERRIDE_PROVIDERS_KEY);
sb.append('=');
sb.append(CollectionUtils.join(parseAddresses(item), ","));
}
return sb.toString();
@ -218,4 +226,11 @@ public class ConfigParser {
}
return addresses;
}
private static URL appendMatchCondition(URL url, ConfigItem item) {
if (item.getMatch() != null) {
url = url.putAttribute(MATCH_CONDITION, item.getMatch());
}
return url;
}
}

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.configurator.parser.model;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.AddressMatch;
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.ListStringMatch;
import java.util.List;
import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
public class ConditionMatch {
private AddressMatch address;
private ListStringMatch service;
private ListStringMatch app;
private List<ParamMatch> param;
public AddressMatch getAddress() {
return address;
}
public void setAddress(AddressMatch address) {
this.address = address;
}
public ListStringMatch getService() {
return service;
}
public void setService(ListStringMatch service) {
this.service = service;
}
public ListStringMatch getApp() {
return app;
}
public void setApp(ListStringMatch app) {
this.app = app;
}
public List<ParamMatch> getParam() {
return param;
}
public void setParam(List<ParamMatch> param) {
this.param = param;
}
public boolean isMatch(URL url) {
if (getAddress() != null && !getAddress().isMatch(url.getAddress())) {
return false;
}
if (getService() != null && !getService().isMatch(url.getServiceKey())) {
return false;
}
if (getApp() != null && !getApp().isMatch(url.getParameter(APPLICATION_KEY))) {
return false;
}
if (getParam() != null) {
for (ParamMatch match : param) {
if (!match.isMatch(url)) {
return false;
}
}
}
return true;
}
@Override
public String toString() {
return "ConditionMatch{" +
"address='" + address + '\'' +
", service='" + service + '\'' +
", app='" + app + '\'' +
", param='" + param + '\'' +
'}';
}
}

View File

@ -16,14 +16,22 @@
*/
package org.apache.dubbo.rpc.cluster.configurator.parser.model;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.PojoUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RECEIVE_RULE;
/**
*
*/
public class ConfigItem {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ConfigItem.class);
public static final String GENERAL_TYPE = "general";
public static final String WEIGHT_TYPE = "weight";
public static final String BALANCING_TYPE = "balancing";
@ -35,6 +43,7 @@ public class ConfigItem {
public static final String SERVICES_KEY = "services";
public static final String APPLICATIONS_KEY = "applications";
public static final String PARAMETERS_KEY = "parameters";
public static final String MATCH_KEY = "match";
public static final String SIDE_KEY = "side";
private String type;
@ -44,6 +53,7 @@ public class ConfigItem {
private List<String> services;
private List<String> applications;
private Map<String, String> parameters;
private ConditionMatch match;
private String side;
@SuppressWarnings("unchecked")
@ -77,14 +87,24 @@ public class ConfigItem {
Object applications = map.get(APPLICATIONS_KEY);
if (applications != null && List.class.isAssignableFrom(applications.getClass())) {
configItem.setApplications(((List<Object>) applications).stream()
.map(String::valueOf).collect(Collectors.toList()));
.map(String::valueOf).collect(Collectors.toList()));
}
Object parameters = map.get(PARAMETERS_KEY);
if (parameters != null && Map.class.isAssignableFrom(parameters.getClass())) {
configItem.setParameters(((Map<String, Object>) parameters).entrySet()
.stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString())));
.stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString())));
}
try {
Object match = map.get(MATCH_KEY);
if (match != null && Map.class.isAssignableFrom(match.getClass())) {
configItem.setMatch(PojoUtils.mapToPojo((Map<String, Object>) match, ConditionMatch.class));
}
} catch (Throwable t) {
logger.error(CLUSTER_FAILED_RECEIVE_RULE, " Failed to parse dynamic configuration rule", String.valueOf(map.get(MATCH_KEY)), "Error occurred when parsing rule component.", t);
}
configItem.setSide((String) map.get(SIDE_KEY));
return configItem;
}
@ -152,4 +172,12 @@ public class ConfigItem {
public void setSide(String side) {
this.side = side;
}
public ConditionMatch getMatch() {
return match;
}
public void setMatch(ConditionMatch match) {
this.match = match;
}
}

View File

@ -24,6 +24,7 @@ import java.util.stream.Collectors;
*
*/
public class ConfiguratorConfig {
public static final String MATCH_CONDITION = "MATCH_CONDITION";
public static final String SCOPE_SERVICE = "service";
public static final String SCOPE_APPLICATION = "application";
public static final String CONFIG_VERSION_KEY = "configVersion";

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.configurator.parser.model;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.StringMatch;
public class ParamMatch {
private String key;
private StringMatch value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public StringMatch getValue() {
return value;
}
public void setValue(StringMatch value) {
this.value = value;
}
public boolean isMatch(URL url) {
if (key == null) {
return false;
}
String input = url.getParameter(key);
return input != null && value.isMatch(input);
}
@Override
public String toString() {
return "ParamMatch{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
'}';
}
}

View File

@ -16,6 +16,32 @@
*/
package org.apache.dubbo.rpc.cluster.directory;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.common.config.Configuration;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.constants.LoggerCodeConstants;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.metrics.event.MetricsEvent;
import org.apache.dubbo.metrics.registry.event.RegistryEvent;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.Router;
import org.apache.dubbo.rpc.cluster.RouterChain;
import org.apache.dubbo.rpc.cluster.SingleRouterChain;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.support.ClusterUtils;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -31,29 +57,6 @@ import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.Version;
import org.apache.dubbo.common.config.Configuration;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.constants.LoggerCodeConstants;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.Router;
import org.apache.dubbo.rpc.cluster.RouterChain;
import org.apache.dubbo.rpc.cluster.SingleRouterChain;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.support.ClusterUtils;
import org.apache.dubbo.rpc.model.ApplicationModel;
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_RECONNECT_TASK_PERIOD;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_RECONNECT_TASK_TRY_COUNT;
@ -128,6 +131,10 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
*/
private final int reconnectTaskPeriod;
private final GlobalMetricsEventMulticaster eventMulticaster;
private ApplicationModel applicationModel;
public AbstractDirectory(URL url) {
this(url, null, false);
}
@ -153,7 +160,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
}
// remove some local only parameters
ApplicationModel applicationModel = url.getOrDefaultApplicationModel();
applicationModel = url.getOrDefaultApplicationModel();
this.queryMap = applicationModel.getBeanFactory().getBean(ClusterUtils.class).mergeLocalParams(queryMap);
if (consumerUrl == null) {
@ -179,12 +186,14 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
this.reconnectTaskTryCount = configuration.getInt(RECONNECT_TASK_TRY_COUNT, DEFAULT_RECONNECT_TASK_TRY_COUNT);
this.reconnectTaskPeriod = configuration.getInt(RECONNECT_TASK_PERIOD, DEFAULT_RECONNECT_TASK_PERIOD);
setRouterChain(routerChain);
eventMulticaster = applicationModel.getBeanFactory().getBean(GlobalMetricsEventMulticaster.class);
}
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory of type " + this.getClass().getSimpleName() + " already destroyed for service " + getConsumerUrl().getServiceKey() + " from registry " + getUrl());
throw new RpcException("Directory of type " + this.getClass().getSimpleName() + " already destroyed for service " + getConsumerUrl().getServiceKey() + " from registry " + getUrl());
}
BitList<Invoker<T>> availableInvokers;
@ -379,8 +388,15 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
invokersToRemove.removeAll(needToRemove);
}
private void publishMetricsEvent(MetricsEvent event) {
if (eventMulticaster != null) {
eventMulticaster.publishEvent(event);
}
}
@Override
public void addDisabledInvoker(Invoker<T> invoker) {
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_DISABLE));
if (invokers.contains(invoker)) {
disabledInvokers.add(invoker);
removeValidInvoker(invoker);
@ -390,6 +406,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
@Override
public void recoverDisabledInvoker(Invoker<T> invoker) {
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_RECOVER_DISABLE));
if (disabledInvokers.remove(invoker)) {
try {
addValidInvoker(invoker);
@ -453,6 +470,7 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
this.invokers = invokers;
refreshInvokerInternal();
this.invokersInitialized = true;
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_CURRENT, invokers.size()));
}
protected void destroyInvokers() {
@ -463,12 +481,14 @@ public abstract class AbstractDirectory<T> implements Directory<T> {
}
private boolean addValidInvoker(Invoker<T> invoker) {
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_VALID));
synchronized (this.validInvokers) {
return this.validInvokers.add(invoker);
}
}
private boolean removeValidInvoker(Invoker<T> invoker) {
publishMetricsEvent(new RegistryEvent.MetricsDirectoryEvent(applicationModel, RegistryEvent.ApplicationType.D_UN_VALID));
synchronized (this.validInvokers) {
return this.validInvokers.remove(invoker);
}

View File

@ -22,7 +22,7 @@ import org.apache.dubbo.rpc.model.ModuleModel;
public class DefaultGovernanceRuleRepositoryImpl implements GovernanceRuleRepository {
private ModuleModel moduleModel;
private final ModuleModel moduleModel;
public DefaultGovernanceRuleRepositoryImpl(ModuleModel moduleModel) {
this.moduleModel = moduleModel;

View File

@ -44,7 +44,7 @@ public class AdaptiveLoadBalance extends AbstractLoadBalance {
//default key
private String attachmentKey = "mem,load";
private AdaptiveMetrics adaptiveMetrics;
private final AdaptiveMetrics adaptiveMetrics;
public AdaptiveLoadBalance(ApplicationModel scopeModel){
adaptiveMetrics = scopeModel.getBeanFactory().getBean(AdaptiveMetrics.class);

View File

@ -61,7 +61,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
// Every invoker has the same weight?
boolean sameWeight = true;
// the maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invoker
// the maxWeight of every invoker, the minWeight = 0 or the maxWeight of the last invoker
int[] weights = new int[length];
// The sum of weights
int totalWeight = 0;
@ -78,7 +78,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
// Return an invoker based on the random value.
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {
return invokers.get(i);

View File

@ -36,11 +36,11 @@ public class RoundRobinLoadBalance extends AbstractLoadBalance {
private static final int RECYCLE_PERIOD = 60000;
private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<>();
protected static class WeightedRoundRobin {
private int weight;
private AtomicLong current = new AtomicLong(0);
private final AtomicLong current = new AtomicLong(0);
private long lastUpdate;
public int getWeight() {

View File

@ -48,9 +48,9 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
private int slidePeriod = 30_000;
private ConcurrentMap<RpcStatus, SlideWindowData> methodMap = new ConcurrentHashMap<>();
private final ConcurrentMap<RpcStatus, SlideWindowData> methodMap = new ConcurrentHashMap<>();
private AtomicBoolean onResetSlideWindow = new AtomicBoolean(false);
private final AtomicBoolean onResetSlideWindow = new AtomicBoolean(false);
private volatile long lastUpdateTime = System.currentTimeMillis();
@ -67,7 +67,7 @@ public class ShortestResponseLoadBalance extends AbstractLoadBalance implements
private long succeededOffset;
private long succeededElapsedOffset;
private RpcStatus rpcStatus;
private final RpcStatus rpcStatus;
public SlideWindowData(RpcStatus rpcStatus) {
this.rpcStatus = rpcStatus;

View File

@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.router;
import java.util.Map;
import static org.apache.dubbo.rpc.cluster.Constants.CONFIG_VERSION_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.DYNAMIC_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.ENABLED_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
@ -40,6 +41,7 @@ public abstract class AbstractRouterRule {
private int priority;
private boolean dynamic = false;
private String version;
private String scope;
private String key;
@ -78,6 +80,7 @@ public abstract class AbstractRouterRule {
setScope((String) map.get(SCOPE_KEY));
setKey((String) map.get(KEY_KEY));
setVersion((String) map.get(CONFIG_VERSION_KEY));
}
public String getRawRule() {
@ -151,4 +154,12 @@ public abstract class AbstractRouterRule {
public void setKey(String key) {
this.key = key;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@ -23,58 +23,68 @@ import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.common.utils.UrlUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Constants;
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
import org.apache.dubbo.rpc.cluster.router.condition.config.AppStateRouter;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import java.text.ParseException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
import static org.apache.dubbo.rpc.cluster.Constants.ADDRESS_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.RUNTIME_KEY;
/**
* ConditionRouter
* It supports the conditional routing configured by "override://", in 2.6.x,
* refer to https://dubbo.apache.org/en/docs/v2.7/user/examples/routing-rule/ .
* For 2.7.x and later, please refer to {@link org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouter}
* and {@link AppStateRouter}
* refer to https://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/ .
* Condition Router directs traffics matching the 'when condition' to a particular address subset determined by the 'then condition'.
* One typical condition rule is like below, with
* 1. the 'when condition' on the left side of '=>' contains matching rule like 'method=sayHello' and 'method=sayHi'
* 2. the 'then condition' on the right side of '=>' contains matching rule like 'region=hangzhou' and 'address=*:20881'
* <p>
* By default, condition router support matching rules like 'foo=bar', 'foo=bar*', 'arguments[0]=bar', 'attachments[foo]=bar', 'attachments[foo]=1~100', etc.
* It's also very easy to add customized matching rules by extending {@link ConditionMatcherFactory}
* and {@link ValuePattern}
* <p>
* ---
* scope: service
* force: true
* runtime: true
* enabled: true
* key: org.apache.dubbo.samples.governance.api.DemoService
* conditions:
* - method=sayHello => region=hangzhou
* - method=sayHi => address=*:20881
* ...
*/
public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
public static final String NAME = "condition";
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractStateRouter.class);
protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
protected static Pattern ARGUMENTS_PATTERN = Pattern.compile("arguments\\[([0-9]+)\\]");
protected Map<String, MatchPair> whenCondition;
protected Map<String, MatchPair> thenCondition;
protected Map<String, ConditionMatcher> whenCondition;
protected Map<String, ConditionMatcher> thenCondition;
protected List<ConditionMatcherFactory> matcherFactories;
private boolean enabled;
private final boolean enabled;
public ConditionStateRouter(URL url, String rule, boolean force, boolean enabled) {
super(url);
this.setForce(force);
this.enabled = enabled;
matcherFactories = moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getActivateExtensions();
if (enabled) {
this.init(rule);
}
@ -84,6 +94,7 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
super(url);
this.setUrl(url);
this.setForce(url.getParameter(FORCE_KEY, false));
matcherFactories = moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getActivateExtensions();
this.enabled = url.getParameter(ENABLED_KEY, true);
if (enabled) {
init(url.getParameterAndDecoded(RULE_KEY));
@ -99,8 +110,8 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
int i = rule.indexOf("=>");
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
Map<String, ConditionMatcher> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<>() : parseRule(whenRule);
Map<String, ConditionMatcher> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
// NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
this.whenCondition = when;
this.thenCondition = then;
@ -109,14 +120,14 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
}
}
private static Map<String, MatchPair> parseRule(String rule)
throws ParseException {
Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
private Map<String, ConditionMatcher> parseRule(String rule)
throws ParseException {
Map<String, ConditionMatcher> condition = new HashMap<>();
if (StringUtils.isBlank(rule)) {
return condition;
}
// Key-Value pair, stores both match and mismatch conditions
MatchPair pair = null;
ConditionMatcher matcherPair = null;
// Multiple values
Set<String> values = null;
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
@ -125,40 +136,40 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
String content = matcher.group(2);
// Start part of the condition expression.
if (StringUtils.isEmpty(separator)) {
pair = new MatchPair();
condition.put(content, pair);
matcherPair = this.getMatcher(content);
condition.put(content, matcherPair);
}
// The KV part of the condition expression
else if ("&".equals(separator)) {
if (condition.get(content) == null) {
pair = new MatchPair();
condition.put(content, pair);
matcherPair = this.getMatcher(content);
condition.put(content, matcherPair);
} else {
pair = condition.get(content);
matcherPair = condition.get(content);
}
}
// The Value in the KV part.
else if ("=".equals(separator)) {
if (pair == null) {
if (matcherPair == null) {
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
}
values = pair.matches;
values = matcherPair.getMatches();
values.add(content);
}
// The Value in the KV part.
else if ("!=".equals(separator)) {
if (pair == null) {
if (matcherPair == null) {
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
}
values = pair.mismatches;
values = matcherPair.getMismatches();
values.add(content);
}
// The Value in the KV part, if Value have more than one items.
@ -240,135 +251,39 @@ public class ConditionStateRouter<T> extends AbstractStateRouter<T> {
return this.getUrl().getParameter(RUNTIME_KEY, false);
}
private ConditionMatcher getMatcher(String key) {
for (ConditionMatcherFactory factory : matcherFactories) {
if (factory.shouldMatch(key)) {
return factory.createMatcher(key, moduleModel);
}
}
return moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getExtension("param").createMatcher(key, moduleModel);
}
boolean matchWhen(URL url, Invocation invocation) {
return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
if (CollectionUtils.isEmptyMap(whenCondition)) {
return true;
}
return doMatch(url, null, invocation, whenCondition, true);
}
private boolean matchThen(URL url, URL param) {
return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
}
private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {
Map<String, String> sample = url.toMap();
boolean result = false;
for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {
String key = matchPair.getKey();
if (key.startsWith(Constants.ARGUMENTS)) {
if (!matchArguments(matchPair, invocation)) {
return false;
} else {
result = true;
continue;
}
}
String sampleValue;
//get real invoked method name from invocation
if (invocation != null && (METHOD_KEY.equals(key) || METHODS_KEY.equals(key))) {
sampleValue = invocation.getMethodName();
} else if (ADDRESS_KEY.equals(key)) {
sampleValue = url.getAddress();
} else if (HOST_KEY.equals(key)) {
sampleValue = url.getHost();
} else {
sampleValue = sample.get(key);
}
if (sampleValue != null) {
if (!matchPair.getValue().isMatch(sampleValue, param)) {
return false;
} else {
result = true;
}
} else {
//not pass the condition
if (!matchPair.getValue().matches.isEmpty()) {
return false;
} else {
result = true;
}
}
}
return result;
}
/**
* analysis the arguments in the rule.
* Examples would be like this:
* "arguments[0]=1", whenCondition is that the first argument is equal to '1'.
* "arguments[1]=a", whenCondition is that the second argument is equal to 'a'.
* @param matchPair
* @param invocation
* @return
*/
public boolean matchArguments(Map.Entry<String, MatchPair> matchPair, Invocation invocation) {
try {
// split the rule
String key = matchPair.getKey();
String[] expressArray = key.split("\\.");
String argumentExpress = expressArray[0];
final Matcher matcher = ARGUMENTS_PATTERN.matcher(argumentExpress);
if (!matcher.find()) {
return false;
}
//extract the argument index
int index = Integer.parseInt(matcher.group(1));
if (index < 0 || index > invocation.getArguments().length) {
return false;
}
//extract the argument value
Object object = invocation.getArguments()[index];
if (matchPair.getValue().isMatch(String.valueOf(object), null)) {
return true;
}
} catch (Exception e) {
logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER,"condition state router arguments match failed","","Arguments match failed, matchPair[]" + matchPair + "] invocation[" + invocation + "]",e);
}
return false;
}
protected static final class MatchPair {
final Set<String> matches = new HashSet<String>();
final Set<String> mismatches = new HashSet<String>();
private boolean isMatch(String value, URL param) {
if (!matches.isEmpty() && mismatches.isEmpty()) {
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
return false;
}
if (!mismatches.isEmpty() && matches.isEmpty()) {
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
return true;
}
if (!matches.isEmpty() && !mismatches.isEmpty()) {
//when both mismatches and matches contain the same value, then using mismatches first
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
return false;
}
if (CollectionUtils.isEmptyMap(thenCondition)) {
return false;
}
return doMatch(url, param, null, thenCondition, false);
}
private boolean doMatch(URL url, URL param, Invocation invocation, Map<String, ConditionMatcher> conditions, boolean isWhenCondition) {
Map<String, String> sample = url.toOriginalMap();
for (Map.Entry<String, ConditionMatcher> entry : conditions.entrySet()) {
ConditionMatcher matchPair = entry.getValue();
if (!matchPair.isMatch(sample, param, invocation, isWhenCondition)) {
return false;
}
}
return true;
}
}

View File

@ -29,8 +29,10 @@ import org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory;
public class AppStateRouterFactory implements StateRouterFactory {
public static final String NAME = "app";
@SuppressWarnings("rawtypes")
private volatile StateRouter router;
@SuppressWarnings("unchecked")
@Override
public <T> StateRouter<T> getRouter(Class<T> interfaceClass, URL url) {
if (router != null) {

View File

@ -48,12 +48,12 @@ import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAIL
*/
public abstract class ListenableStateRouter<T> extends AbstractStateRouter<T> implements ConfigurationListener {
public static final String NAME = "LISTENABLE_ROUTER";
private static final String RULE_SUFFIX = ".condition-router";
public static final String RULE_SUFFIX = ".condition-router";
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ListenableStateRouter.class);
private volatile ConditionRouterRule routerRule;
private volatile List<ConditionStateRouter<T>> conditionRouters = Collections.emptyList();
private String ruleKey;
private final String ruleKey;
public ListenableStateRouter(URL url, String ruleKey) {
super(url);

View File

@ -0,0 +1,77 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.config;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY;
import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
/**
* Application level router, "application.condition-router"
*/
public class ProviderAppStateRouter<T> extends ListenableStateRouter<T> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ListenableStateRouter.class);
public static final String NAME = "PROVIDER_APP_ROUTER";
private String application;
private final String currentApplication;
public ProviderAppStateRouter(URL url) {
super(url, url.getApplication());
this.currentApplication = url.getApplication();
}
@Override
public void notify(BitList<Invoker<T>> invokers) {
if (CollectionUtils.isEmpty(invokers)) {
return;
}
Invoker<T> invoker = invokers.get(0);
URL url = invoker.getUrl();
String providerApplication = url.getRemoteApplication();
// provider application is empty or equals with the current application
if (isEmpty(providerApplication) || providerApplication.equals(currentApplication)) {
logger.warn(CLUSTER_TAG_ROUTE_EMPTY, "condition router get providerApplication is empty, will not subscribe to provider app rules.", "", "");
return;
}
synchronized (this) {
if (!providerApplication.equals(application)) {
if (StringUtils.isNotEmpty(application)) {
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
}
String key = providerApplication + RULE_SUFFIX;
this.getRuleRepository().addListener(key, this);
application = providerApplication;
String rawRule = this.getRuleRepository().getRule(key, DynamicConfiguration.DEFAULT_GROUP);
if (StringUtils.isNotEmpty(rawRule)) {
this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
}
}
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.config;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.cluster.router.state.CacheableStateRouterFactory;
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
/**
* Tag router factory
*/
@Activate(order = 145)
public class ProviderAppStateRouterFactory extends CacheableStateRouterFactory {
public static final String NAME = "provider-app";
@Override
protected <T> StateRouter<T> createRouter(Class<T> interfaceClass, URL url) {
return new ProviderAppStateRouter<>(url);
}
}

View File

@ -0,0 +1,138 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
import org.apache.dubbo.rpc.model.ModuleModel;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
/**
* The abstract implementation of ConditionMatcher, records the match and mismatch patterns of this matcher while at the same time
* provides the common match logics.
*/
public abstract class AbstractConditionMatcher implements ConditionMatcher {
public static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractConditionMatcher.class);
public static final String DOES_NOT_FOUND_VALUE = "dubbo_internal_not_found_argument_condition_value";
final Set<String> matches = new HashSet<>();
final Set<String> mismatches = new HashSet<>();
private final ModuleModel model;
private final List<ValuePattern> valueMatchers;
protected final String key;
public AbstractConditionMatcher(String key, ModuleModel model) {
this.key = key;
this.model = model;
this.valueMatchers = model.getExtensionLoader(ValuePattern.class).getActivateExtensions();
}
public static String getSampleValueFromUrl(String conditionKey, Map<String, String> sample, URL param, Invocation invocation) {
String sampleValue;
//get real invoked method name from invocation
if (invocation != null && (METHOD_KEY.equals(conditionKey) || METHODS_KEY.equals(conditionKey))) {
sampleValue = invocation.getMethodName();
} else {
sampleValue = sample.get(conditionKey);
}
return sampleValue;
}
public boolean isMatch(Map<String, String> sample, URL param, Invocation invocation, boolean isWhenCondition) {
String value = getValue(sample, param, invocation);
if (value == null) {
// if key does not present in whichever of url, invocation or attachment based on the matcher type, then return false.
return false;
}
if (!matches.isEmpty() && mismatches.isEmpty()) {
for (String match : matches) {
if (doPatternMatch(match, value, param, invocation, isWhenCondition)) {
return true;
}
}
return false;
}
if (!mismatches.isEmpty() && matches.isEmpty()) {
for (String mismatch : mismatches) {
if (doPatternMatch(mismatch, value, param, invocation, isWhenCondition)) {
return false;
}
}
return true;
}
if (!matches.isEmpty() && !mismatches.isEmpty()) {
//when both mismatches and matches contain the same value, then using mismatches first
for (String mismatch : mismatches) {
if (doPatternMatch(mismatch, value, param, invocation, isWhenCondition)) {
return false;
}
}
for (String match : matches) {
if (doPatternMatch(match, value, param, invocation, isWhenCondition)) {
return true;
}
}
return false;
}
return false;
}
@Override
public Set<String> getMatches() {
return matches;
}
@Override
public Set<String> getMismatches() {
return mismatches;
}
// range, equal or other methods
protected boolean doPatternMatch(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition) {
for (ValuePattern valueMatcher : valueMatchers) {
if (valueMatcher.shouldMatch(pattern)) {
return valueMatcher.match(pattern, value, url, invocation, isWhenCondition);
}
}
// this should never happen.
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Executing condition rule value match expression error.", "pattern is " + pattern + ", value is " + value + ", condition type " + (isWhenCondition ? "when" : "then"), "There should at least has one ValueMatcher instance that applies to all patterns, will force to use wildcard matcher now.");
ValuePattern paramValueMatcher = model.getExtensionLoader(ValuePattern.class).getExtension("wildcard");
return paramValueMatcher.match(pattern, value, url, invocation, isWhenCondition);
}
/**
* Used to get value from different places of the request context, for example, url, attachment and invocation.
* This makes condition rule possible to check values in any place of a request.
*/
protected abstract String getValue(Map<String, String> sample, URL url, Invocation invocation);
}

View File

@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import java.util.Map;
import java.util.Set;
/**
* ConditionMatcher represents a specific match condition of a condition rule.
* <p>
* The following condition rule '=bar&arguments[0]=hello* => region=hangzhou' consists of three ConditionMatchers:
* 1. UrlParamConditionMatcher represented by 'foo=bar'
* 2. ArgumentsConditionMatcher represented by 'arguments[0]=hello*'
* 3. UrlParamConditionMatcher represented by 'region=hangzhou'
* <p>
* It's easy to define your own matcher by extending {@link ConditionMatcherFactory}
*/
public interface ConditionMatcher {
/**
* Determines if the patterns of this matcher matches with request context.
*
* @param sample request context in provider url
* @param param request context in consumer url
* @param invocation request context in invocation, typically, service, method, arguments and attachments
* @param isWhenCondition condition type
* @return the matching result
*/
boolean isMatch(Map<String, String> sample, URL param, Invocation invocation, boolean isWhenCondition);
/**
* match patterns extracted from when condition
*
* @return
*/
Set<String> getMatches();
/**
* mismatch patterns extracted from then condition
*
* @return
*/
Set<String> getMismatches();
}

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.rpc.model.ModuleModel;
/**
* Factory of ConditionMatcher instances.
*/
@SPI
public interface ConditionMatcherFactory {
/**
* Check if the key is of the form of the current matcher type which this factory instance represents..
*
* @param key the key of a particular form
* @return true if matches, otherwise false
*/
boolean shouldMatch(String key);
/**
* Create a matcher instance for the key.
*
* @param key the key value conforms to a specific matcher specification
* @param model module model
* @return the specific matcher instance
*/
ConditionMatcher createMatcher(String key, ModuleModel model);
}

View File

@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.argument;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.AbstractConditionMatcher;
import org.apache.dubbo.rpc.model.ModuleModel;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
/**
* analysis the arguments in the rule.
* Examples would be like this:
* "arguments[0]=1", whenCondition is that the first argument is equal to '1'.
* "arguments[1]=a", whenCondition is that the second argument is equal to 'a'.
*/
@Activate
public class ArgumentConditionMatcher extends AbstractConditionMatcher {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ArgumentConditionMatcher.class);
private static final Pattern ARGUMENTS_PATTERN = Pattern.compile("arguments\\[([0-9]+)\\]");
public ArgumentConditionMatcher(String key, ModuleModel model) {
super(key, model);
}
@Override
public String getValue(Map<String, String> sample, URL url, Invocation invocation) {
try {
// split the rule
String[] expressArray = key.split("\\.");
String argumentExpress = expressArray[0];
final Matcher matcher = ARGUMENTS_PATTERN.matcher(argumentExpress);
if (!matcher.find()) {
return DOES_NOT_FOUND_VALUE;
}
//extract the argument index
int index = Integer.parseInt(matcher.group(1));
if (index < 0 || index > invocation.getArguments().length) {
return DOES_NOT_FOUND_VALUE;
}
//extract the argument value
return String.valueOf(invocation.getArguments()[index]);
} catch (Exception e) {
logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Parse argument match condition failed", "", "Invalid , will ignore., ", e);
}
return DOES_NOT_FOUND_VALUE;
}
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.argument;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.cluster.Constants;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
import org.apache.dubbo.rpc.model.ModuleModel;
@Activate(order = 300)
public class ArgumentConditionMatcherFactory implements ConditionMatcherFactory {
@Override
public boolean shouldMatch(String key) {
return key.startsWith(Constants.ARGUMENTS);
}
@Override
public ConditionMatcher createMatcher(String key, ModuleModel model) {
return new ArgumentConditionMatcher(key, model);
}
}

View File

@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.attachment;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.AbstractConditionMatcher;
import org.apache.dubbo.rpc.model.ModuleModel;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
/**
* analysis the arguments in the rule.
* Examples would be like this:
* "attachments[foo]=bar", whenCondition is that the attachment value of 'foo' is equal to 'bar'.
*/
@Activate
public class AttachmentConditionMatcher extends AbstractConditionMatcher {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AttachmentConditionMatcher.class);
private static final Pattern ATTACHMENTS_PATTERN = Pattern.compile("attachments\\[(.+)\\]");
public AttachmentConditionMatcher(String key, ModuleModel model) {
super(key, model);
}
@Override
protected String getValue(Map<String, String> sample, URL url, Invocation invocation) {
try {
// split the rule
String[] expressArray = key.split("\\.");
String argumentExpress = expressArray[0];
final Matcher matcher = ATTACHMENTS_PATTERN.matcher(argumentExpress);
if (!matcher.find()) {
return DOES_NOT_FOUND_VALUE;
}
//extract the argument index
String attachmentKey = matcher.group(1);
if (StringUtils.isEmpty(attachmentKey)) {
return DOES_NOT_FOUND_VALUE;
}
//extract the argument value
return invocation.getAttachment(attachmentKey);
} catch (Exception e) {
logger.warn(CLUSTER_FAILED_EXEC_CONDITION_ROUTER,
"condition state router attachment match failed",
"",
"Invalid match condition: " + key,
e);
}
return DOES_NOT_FOUND_VALUE;
}
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.attachment;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
import org.apache.dubbo.rpc.model.ModuleModel;
@Activate(order = 200)
public class AttachmentConditionMatcherFactory implements ConditionMatcherFactory {
private static final String ATTACHMENTS = "attachments";
@Override
public boolean shouldMatch(String key) {
return key.startsWith(ATTACHMENTS);
}
@Override
public ConditionMatcher createMatcher(String key, ModuleModel model) {
return new AttachmentConditionMatcher(key, model);
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.param;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.AbstractConditionMatcher;
import org.apache.dubbo.rpc.model.ModuleModel;
import java.util.Map;
/**
* This instance will be loaded separately to ensure it always gets executed as the last matcher.
* So we don't put Active annotation here.
*/
public class UrlParamConditionMatcher extends AbstractConditionMatcher {
public UrlParamConditionMatcher(String key, ModuleModel model) {
super(key, model);
}
@Override
protected String getValue(Map<String, String> sample, URL url, Invocation invocation) {
return getSampleValueFromUrl(key, sample, url, invocation);
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.param;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
import org.apache.dubbo.rpc.model.ModuleModel;
// Make sure this is the last matcher being executed.
@Activate(order = Integer.MAX_VALUE)
public class UrlParamConditionMatcherFactory implements ConditionMatcherFactory {
@Override
public boolean shouldMatch(String key) {
return true;
}
@Override
public ConditionMatcher createMatcher(String key, ModuleModel model) {
return new UrlParamConditionMatcher(key, model);
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.rpc.Invocation;
/**
*
*/
@SPI
public interface ValuePattern {
/**
* Is the input pattern of a specific form, for example, range pattern '1~100', wildcard pattern 'hello*', etc.
*
* @param pattern the match or mismatch pattern
* @return true or false
*/
boolean shouldMatch(String pattern);
/**
* Is the pattern matches with the request context
*
* @param pattern pattern value extracted from condition rule
* @param value the real value extracted from request context
* @param url request context in consumer url
* @param invocation request context in invocation
* @param isWhenCondition condition type
* @return true if successfully match
*/
boolean match(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition);
}

View File

@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.range;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
/**
* Matches with patterns like 'key=1~100', 'key=~100' or 'key=1~'
*/
@Activate(order = 100)
public class RangeValuePattern implements ValuePattern {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RangeValuePattern.class);
@Override
public boolean shouldMatch(String pattern) {
return pattern.contains("~");
}
@Override
public boolean match(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition) {
boolean defaultValue = !isWhenCondition;
try {
int intValue = StringUtils.parseInteger(value);
String[] arr = pattern.split("~");
if (arr.length < 2) {
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "", "", "Invalid condition rule " + pattern + " or value " + value + ", will ignore.");
return defaultValue;
}
String rawStart = arr[0];
String rawEnd = arr[1];
if (StringUtils.isEmpty(rawStart) && StringUtils.isEmpty(rawEnd)) {
return defaultValue;
}
if (StringUtils.isEmpty(rawStart)) {
int end = StringUtils.parseInteger(rawEnd);
if (intValue > end) {
return false;
}
} else if (StringUtils.isEmpty(rawEnd)) {
int start = StringUtils.parseInteger(rawStart);
if (intValue < start) {
return false;
}
} else {
int start = StringUtils.parseInteger(rawStart);
int end = StringUtils.parseInteger(rawEnd);
if (intValue < start || intValue > end) {
return false;
}
}
} catch (Exception e) {
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Parse integer error", "", "Invalid condition rule " + pattern + " or value " + value + ", will ignore.", e);
return defaultValue;
}
return true;
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.wildcard;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.UrlUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.ValuePattern;
/**
* Matches with patterns like 'key=hello', 'key=hello*', 'key=*hello', 'key=h*o' or 'key=*'
* <p>
* This pattern evaluator must be the last one being executed.
*/
@Activate(order = Integer.MAX_VALUE)
public class WildcardValuePattern implements ValuePattern {
@Override
public boolean shouldMatch(String key) {
return true;
}
@Override
public boolean match(String pattern, String value, URL url, Invocation invocation, boolean isWhenCondition) {
return UrlUtils.isMatchGlobPattern(pattern, value, url);
}
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.dubbo.rpc.cluster.router.mesh.route;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invoker;
@ -109,7 +110,7 @@ public class MeshRuleCache<T> {
List<Invoker<T>> subsetInvokers = subsetMap.computeIfAbsent(subsetName, (k) -> new BitList<>(invokers.getOriginList(), true));
Map<String, String> labels = subset.getLabels();
if (containMapKeyValue(invoker.getUrl().getServiceParameters(protocolServiceKey), labels)) {
if (isLabelMatch(invoker.getUrl(), protocolServiceKey, labels)) {
subsetInvokers.add(invoker);
matched = true;
}
@ -133,7 +134,7 @@ public class MeshRuleCache<T> {
return new MeshRuleCache<>(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), BitList.emptyList());
}
protected static boolean containMapKeyValue(Map<String, String> originMap, Map<String, String> inputMap) {
protected static boolean isLabelMatch(URL url, String protocolServiceKey, Map<String, String> inputMap) {
if (inputMap == null || inputMap.size() == 0) {
return true;
}
@ -142,7 +143,7 @@ public class MeshRuleCache<T> {
String key = entry.getKey();
String value = entry.getValue();
String originMapValue = originMap.get(key);
String originMapValue = url.getOriginalServiceParameter(protocolServiceKey, key);
if (!value.equals(originMapValue)) {
return false;
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import java.net.UnknownHostException;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
import static org.apache.dubbo.common.utils.NetUtils.matchIpExpression;
import static org.apache.dubbo.common.utils.UrlUtils.isMatchGlobPattern;
public class AddressMatch {
public static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AddressMatch.class);
private String wildcard;
private String cird;
private String exact;
public String getWildcard() {
return wildcard;
}
public void setWildcard(String wildcard) {
this.wildcard = wildcard;
}
public String getCird() {
return cird;
}
public void setCird(String cird) {
this.cird = cird;
}
public String getExact() {
return exact;
}
public void setExact(String exact) {
this.exact = exact;
}
public boolean isMatch(String input) {
if (getCird() != null && input != null) {
try {
return input.equals(getCird()) || matchIpExpression(getCird(), input);
} catch (UnknownHostException e) {
logger.error(CLUSTER_FAILED_EXEC_CONDITION_ROUTER, "Executing routing rule match expression error.", "", String.format("Error trying to match cird formatted address %s with input %s in AddressMatch.", getCird(), input), e);
}
}
if (getWildcard() != null && input != null) {
if (ANYHOST_VALUE.equals(getWildcard()) || ANY_VALUE.equals(getWildcard())) {
return true;
}
// FIXME
return isMatchGlobPattern(getWildcard(), input);
}
if (getExact() != null && input != null) {
return input.equals(getExact());
}
return false;
}
}

View File

@ -18,12 +18,15 @@
package org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match;
import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
public class StringMatch {
private String exact;
private String prefix;
private String regex;
private String noempty;
private String empty;
private String wildcard;
public String getExact() {
@ -66,6 +69,14 @@ public class StringMatch {
this.empty = empty;
}
public String getWildcard() {
return wildcard;
}
public void setWildcard(String wildcard) {
this.wildcard = wildcard;
}
public boolean isMatch(String input) {
if (getExact() != null && input != null) {
return input.equals(getExact());
@ -73,6 +84,9 @@ public class StringMatch {
return input.startsWith(getPrefix());
} else if (getRegex() != null && input != null) {
return input.matches(getRegex());
} else if (getWildcard() != null && input != null) {
// only supports "*"
return input.equals(getWildcard()) || ANY_VALUE.equals(getWildcard());
} else if (getEmpty() != null) {
return input == null || "".equals(input);
} else if (getNoempty() != null) {
@ -82,7 +96,6 @@ public class StringMatch {
}
}
@Override
public String toString() {
return "StringMatch{" +

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.script.config;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.cluster.router.state.CacheableStateRouterFactory;
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
@Activate(order = 200)
public class AppScriptRouterFactory extends CacheableStateRouterFactory {
public static final String NAME = "script";
@Override
protected <T> StateRouter<T> createRouter(Class<T> interfaceClass, URL url) {
return new AppScriptStateRouter<>(url);
}
}

View File

@ -0,0 +1,150 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.script.config;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
import org.apache.dubbo.common.config.configcenter.ConfigurationListener;
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
import org.apache.dubbo.rpc.cluster.router.script.ScriptStateRouter;
import org.apache.dubbo.rpc.cluster.router.script.config.model.ScriptRule;
import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_INVALID;
import static org.apache.dubbo.common.utils.StringUtils.isEmpty;
import static org.apache.dubbo.rpc.cluster.Constants.DEFAULT_SCRIPT_TYPE_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.RUNTIME_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.TYPE_KEY;
public class AppScriptStateRouter<T> extends AbstractStateRouter<T> implements ConfigurationListener {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AppScriptStateRouter.class);
private static final String RULE_SUFFIX = ".script-router";
private ScriptRule scriptRule;
private ScriptStateRouter<T> scriptRouter;
private String application;
public AppScriptStateRouter(URL url) {
super(url);
}
@Override
protected BitList<Invoker<T>> doRoute(BitList<Invoker<T>> invokers,
URL url,
Invocation invocation,
boolean needToPrintMessage,
Holder<RouterSnapshotNode<T>> routerSnapshotNodeHolder,
Holder<String> messageHolder) throws RpcException {
if (scriptRouter == null || !scriptRule.isValid() || !scriptRule.isEnabled()) {
if (needToPrintMessage) {
messageHolder.set("Directly return from script router. Reason: Invokers from previous router is empty or script is not enabled. Script rule is: " + (scriptRule == null ? "null" : scriptRule.getRawRule()));
}
return invokers;
}
invokers = scriptRouter.route(invokers, url, invocation, needToPrintMessage, routerSnapshotNodeHolder);
if (needToPrintMessage) {
messageHolder.set(messageHolder.get());
}
return invokers;
}
@Override
public synchronized void process(ConfigChangedEvent event) {
if (logger.isDebugEnabled()) {
logger.debug("Notification of script rule change, type is: " + event.getChangeType() + ", raw rule is:\n " +
event.getContent());
}
try {
if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
this.scriptRule = null;
} else {
this.scriptRule = ScriptRule.parse(event.getContent());
URL scriptUrl = getUrl()
.addParameter(TYPE_KEY, isEmpty(scriptRule.getType()) ? DEFAULT_SCRIPT_TYPE_KEY : scriptRule.getType())
.addParameterAndEncoded(RULE_KEY, scriptRule.getScript())
.addParameter(FORCE_KEY, scriptRule.isForce())
.addParameter(RUNTIME_KEY, scriptRule.isRuntime());
scriptRouter = new ScriptStateRouter<>(scriptUrl);
}
} catch (Exception e) {
logger.error(CLUSTER_TAG_ROUTE_INVALID, "Failed to parse the raw tag router rule", "", "Failed to parse the raw tag router rule and it will not take effect, please check if the " +
"rule matches with the template, the raw rule is:\n ", e);
}
}
@Override
public void notify(BitList<Invoker<T>> invokers) {
if (CollectionUtils.isEmpty(invokers)) {
return;
}
Invoker<T> invoker = invokers.get(0);
URL url = invoker.getUrl();
String providerApplication = url.getRemoteApplication();
if (isEmpty(providerApplication)) {
logger.error(CLUSTER_TAG_ROUTE_EMPTY, "tag router get providerApplication is empty", "", "TagRouter must getConfig from or subscribe to a specific application, but the application " +
"in this TagRouter is not specified.");
return;
}
synchronized (this) {
if (!providerApplication.equals(application)) {
if (StringUtils.isNotEmpty(application)) {
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
}
String key = providerApplication + RULE_SUFFIX;
this.getRuleRepository().addListener(key, this);
application = providerApplication;
String rawRule = this.getRuleRepository().getRule(key, DynamicConfiguration.DEFAULT_GROUP);
if (StringUtils.isNotEmpty(rawRule)) {
this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
}
}
}
}
@Override
public void stop() {
if (StringUtils.isNotEmpty(application)) {
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
}
}
// for testing purpose
public void setScriptRule(ScriptRule scriptRule) {
this.scriptRule = scriptRule;
}
}

View File

@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.script.config.model;
import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import java.util.Map;
public class ScriptRule extends AbstractRouterRule {
private static final String TYPE_KEY = "type";
private static final String SCRIPT_KEY = "script";
private String type;
private String script;
public static ScriptRule parse(String rawRule) {
Yaml yaml = new Yaml(new SafeConstructor());
Map<String, Object> map = yaml.load(rawRule);
ScriptRule rule = new ScriptRule();
rule.parseFromMap0(map);
rule.setRawRule(rawRule);
Object rawType = map.get(TYPE_KEY);
if (rawType != null) {
rule.setType((String) rawType);
}
Object rawScript = map.get(SCRIPT_KEY);
if (rawScript != null) {
rule.setScript((String) rawScript);
} else {
rule.setValid(false);
}
return rule;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getScript() {
return script;
}
public void setScript(String script) {
this.script = script;
}
}

View File

@ -43,8 +43,10 @@ public abstract class AbstractStateRouter<T> implements StateRouter<T> {
*/
private final boolean shouldFailFast;
protected ModuleModel moduleModel;
public AbstractStateRouter(URL url) {
ModuleModel moduleModel = url.getOrDefaultModuleModel();
moduleModel = url.getOrDefaultModuleModel();
this.ruleRepository = moduleModel.getExtensionLoader(GovernanceRuleRepository.class).getDefaultExtension();
this.url = url;
this.shouldFailFast = Boolean.parseBoolean(ConfigurationUtils.getProperty(moduleModel, Constants.SHOULD_FAIL_FAST_KEY, "true"));

View File

@ -52,7 +52,7 @@ import java.util.concurrent.ThreadLocalRandom;
* @param <E>
* @since 3.0
*/
public class BitList<E> extends AbstractList<E> {
public class BitList<E> extends AbstractList<E> implements Cloneable {
private final BitSet rootSet;
private volatile List<E> originList;
private final static BitList emptyList = new BitList(Collections.emptyList());

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.cluster.router.state;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@ -29,6 +30,6 @@ public interface StateRouterFactory {
* @return router instance
* @since 3.0
*/
@Adaptive("protocol")
@Adaptive(CommonConstants.PROTOCOL_KEY)
<T> StateRouter<T> getRouter(Class<T> interfaceClass, URL url);
}

View File

@ -36,7 +36,7 @@ import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.router.tag.model.TagRouterRule;
import org.apache.dubbo.rpc.cluster.router.tag.model.TagRuleParser;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
@ -53,8 +53,9 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(TagStateRouter.class);
private static final String RULE_SUFFIX = ".tag-router";
private TagRouterRule tagRouterRule;
private volatile TagRouterRule tagRouterRule;
private String application;
private volatile BitList<Invoker<T>> invokers = BitList.emptyList();
public TagStateRouter(URL url) {
super(url);
@ -71,7 +72,9 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
this.tagRouterRule = null;
} else {
this.tagRouterRule = TagRuleParser.parse(event.getContent());
TagRouterRule rule = TagRuleParser.parse(event.getContent());
rule.init(this);
this.tagRouterRule = rule;
}
} catch (Exception e) {
logger.error(CLUSTER_TAG_ROUTE_INVALID,"Failed to parse the raw tag router rule","","Failed to parse the raw tag router rule and it will not take effect, please check if the " +
@ -103,9 +106,9 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
// if we are requesting for a Provider with a specific tag
if (StringUtils.isNotEmpty(tag)) {
List<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
Set<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
// filter by dynamic tag group first
if (CollectionUtils.isNotEmpty(addresses)) {
if (addresses != null) { // null means tag not set
result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses));
// if result is not null OR it's null but force=true, return result directly
if (CollectionUtils.isNotEmpty(result) || tagRouterRuleCopy.isForce()) {
@ -129,8 +132,10 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
}
// FAILOVER: return all Providers without any tags.
else {
BitList<Invoker<T>> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
tagRouterRuleCopy.getAddresses()));
BitList<Invoker<T>> tmp = filterInvoker(
invokers,
invoker -> addressNotMatches(invoker.getUrl(), tagRouterRuleCopy.getAddresses())
);
if (needToPrintMessage) {
messageHolder.set("FAILOVER: return all Providers without any tags");
}
@ -139,7 +144,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
} else {
// List<String> addresses = tagRouterRule.filter(providerApp);
// return all addresses in dynamic tag group.
List<String> addresses = tagRouterRuleCopy.getAddresses();
Set<String> addresses = tagRouterRuleCopy.getAddresses();
if (CollectionUtils.isNotEmpty(addresses)) {
result = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(), addresses));
// 1. all addresses are in dynamic tag group, return empty list.
@ -157,7 +162,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
}
return filterInvoker(result, invoker -> {
String localTag = invoker.getUrl().getParameter(TAG_KEY);
return StringUtils.isEmpty(localTag) || !tagRouterRuleCopy.getTagNames().contains(localTag);
return StringUtils.isEmpty(localTag);
});
}
}
@ -219,15 +224,15 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
return newInvokers;
}
private boolean addressMatches(URL url, List<String> addresses) {
private boolean addressMatches(URL url, Set<String> addresses) {
return addresses != null && checkAddressMatch(addresses, url.getHost(), url.getPort());
}
private boolean addressNotMatches(URL url, List<String> addresses) {
private boolean addressNotMatches(URL url, Set<String> addresses) {
return addresses == null || !checkAddressMatch(addresses, url.getHost(), url.getPort());
}
private boolean checkAddressMatch(List<String> addresses, String host, int port) {
private boolean checkAddressMatch(Set<String> addresses, String host, int port) {
for (String address : addresses) {
try {
if (NetUtils.matchIpExpression(address, host, port)) {
@ -237,7 +242,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
return true;
}
} catch (Exception e) {
logger.error(CLUSTER_TAG_ROUTE_INVALID,"tag route address is invalid","","The format of ip address is invalid in tag route. Address :" + address,e);
logger.error(CLUSTER_TAG_ROUTE_INVALID, "tag route address is invalid", "", "The format of ip address is invalid in tag route. Address :" + address, e);
}
}
return false;
@ -249,6 +254,7 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
@Override
public void notify(BitList<Invoker<T>> invokers) {
this.invokers = invokers;
if (CollectionUtils.isEmpty(invokers)) {
return;
}
@ -275,14 +281,29 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu
if (StringUtils.isNotEmpty(rawRule)) {
this.process(new ConfigChangedEvent(key, DynamicConfiguration.DEFAULT_GROUP, rawRule));
}
} else {
if (this.tagRouterRule != null) {
TagRouterRule newRule = TagRuleParser.parse(this.tagRouterRule.getRawRule());
newRule.init(this);
this.tagRouterRule = newRule;
}
}
}
}
public BitList<Invoker<T>> getInvokers() {
return invokers;
}
@Override
public void stop() {
if (StringUtils.isNotEmpty(application)) {
this.getRuleRepository().removeListener(application + RULE_SUFFIX, this);
}
}
// for testing purpose
public void setTagRouterRule(TagRouterRule tagRouterRule) {
this.tagRouterRule = tagRouterRule;
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.tag.model;
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.StringMatch;
public class ParamMatch {
private String key;
private StringMatch value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public StringMatch getValue() {
return value;
}
public void setValue(StringMatch value) {
this.value = value;
}
public boolean isMatch(String input) {
if (getValue() != null && input != null) {
return getValue().isMatch(input);
}
return false;
}
}

View File

@ -16,19 +16,44 @@
*/
package org.apache.dubbo.rpc.cluster.router.tag.model;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.PojoUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RULE_PARSING;
import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V30;
public class Tag {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(Tag.class);
private String name;
private List<ParamMatch> match;
private List<String> addresses;
@SuppressWarnings("unchecked")
public static Tag parseFromMap(Map<String, Object> map) {
public static Tag parseFromMap(Map<String, Object> map, String version) {
Tag tag = new Tag();
tag.setName((String) map.get("name"));
if (version != null && version.startsWith(RULE_VERSION_V30)) {
if (map.get("match") != null) {
tag.setMatch(((List<Map<String, Object>>) map.get("match")).stream().map((objectMap) -> {
try {
return PojoUtils.mapToPojo(objectMap, ParamMatch.class);
} catch (ReflectiveOperationException e) {
logger.error(CLUSTER_FAILED_RULE_PARSING, " Failed to parse tag rule ", String.valueOf(objectMap), "Error occurred when parsing rule component.", e);
}
return null;
}).collect(Collectors.toList()));
} else {
logger.warn(CLUSTER_FAILED_RULE_PARSING, "", String.valueOf(map), "It's recommended to use 'match' instead of 'addresses' for v3.0 tag rule.");
}
}
Object addresses = map.get("addresses");
if (addresses != null && List.class.isAssignableFrom(addresses.getClass())) {
tag.setAddresses(((List<Object>) addresses).stream().map(String::valueOf).collect(Collectors.toList()));
@ -52,4 +77,13 @@ public class Tag {
public void setAddresses(List<String> addresses) {
this.addresses = addresses;
}
public List<ParamMatch> getMatch() {
return match;
}
public void setMatch(List<ParamMatch> match) {
this.match = match;
}
}

View File

@ -17,14 +17,19 @@
package org.apache.dubbo.rpc.cluster.router.tag.model;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.router.tag.TagStateRouter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V30;
import static org.apache.dubbo.rpc.cluster.Constants.TAGS_KEY;
/**
@ -45,8 +50,8 @@ import static org.apache.dubbo.rpc.cluster.Constants.TAGS_KEY;
public class TagRouterRule extends AbstractRouterRule {
private List<Tag> tags;
private final Map<String, List<String>> addressToTagnames = new HashMap<>();
private final Map<String, List<String>> tagnameToAddresses = new HashMap<>();
private final Map<String, Set<String>> addressToTagnames = new HashMap<>();
private final Map<String, Set<String>> tagnameToAddresses = new HashMap<>();
@SuppressWarnings("unchecked")
public static TagRouterRule parseFromMap(Map<String, Object> map) {
@ -56,43 +61,71 @@ public class TagRouterRule extends AbstractRouterRule {
Object tags = map.get(TAGS_KEY);
if (tags != null && List.class.isAssignableFrom(tags.getClass())) {
tagRouterRule.setTags(((List<Map<String, Object>>) tags).stream()
.map(Tag::parseFromMap).collect(Collectors.toList()));
.map(objMap -> Tag.parseFromMap(objMap, tagRouterRule.getVersion())).collect(Collectors.toList()));
}
return tagRouterRule;
}
public void init() {
public void init(TagStateRouter<?> router) {
if (!isValid()) {
return;
}
BitList<? extends Invoker<?>> invokers = router.getInvokers();
// for tags with 'addresses` field set and 'match' field not set
tags.stream().filter(tag -> CollectionUtils.isNotEmpty(tag.getAddresses())).forEach(tag -> {
tagnameToAddresses.put(tag.getName(), tag.getAddresses());
tagnameToAddresses.put(tag.getName(), new HashSet<>(tag.getAddresses()));
tag.getAddresses().forEach(addr -> {
List<String> tagNames = addressToTagnames.computeIfAbsent(addr, k -> new ArrayList<>());
Set<String> tagNames = addressToTagnames.computeIfAbsent(addr, k -> new HashSet<>());
tagNames.add(tag.getName());
});
});
if (this.getVersion() != null && this.getVersion().startsWith(RULE_VERSION_V30)) {
// for tags with 'match` field set and 'addresses' field not set
if (CollectionUtils.isNotEmpty(invokers)) {
tags.stream().filter(tag -> CollectionUtils.isEmpty(tag.getAddresses())).forEach(tag -> {
Set<String> addresses = new HashSet<>();
List<ParamMatch> paramMatchers = tag.getMatch();
invokers.forEach(invoker -> {
boolean isMatch = true;
for (ParamMatch matcher : paramMatchers) {
if (!matcher.isMatch(invoker.getUrl().getOriginalParameter(matcher.getKey()))) {
isMatch = false;
break;
}
}
if (isMatch) {
addresses.add(invoker.getUrl().getAddress());
}
});
if (CollectionUtils.isNotEmpty(addresses)) {// null means tag not set
tagnameToAddresses.put(tag.getName(), addresses);
}
});
}
}
}
public List<String> getAddresses() {
return tags.stream()
.filter(tag -> CollectionUtils.isNotEmpty(tag.getAddresses()))
.flatMap(tag -> tag.getAddresses().stream())
.collect(Collectors.toList());
public Set<String> getAddresses() {
return tagnameToAddresses.entrySet().stream()
.filter(entry -> CollectionUtils.isNotEmpty(entry.getValue()))
.flatMap(entry -> entry.getValue().stream())
.collect(Collectors.toSet());
}
public List<String> getTagNames() {
return tags.stream().map(Tag::getName).collect(Collectors.toList());
}
public Map<String, List<String>> getAddressToTagnames() {
public Map<String, Set<String>> getAddressToTagnames() {
return addressToTagnames;
}
public Map<String, List<String>> getTagnameToAddresses() {
public Map<String, Set<String>> getTagnameToAddresses() {
return tagnameToAddresses;
}

View File

@ -24,7 +24,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
import java.util.Map;
/**
*
* Parse raw rule into structured tag rule
*/
public class TagRuleParser {
@ -37,7 +37,6 @@ public class TagRuleParser {
rule.setValid(false);
}
rule.init();
return rule;
}
}

View File

@ -70,7 +70,7 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
private volatile boolean enableConnectivityValidation = true;
private AtomicBoolean destroyed = new AtomicBoolean(false);
private final AtomicBoolean destroyed = new AtomicBoolean(false);
private volatile Invoker<T> stickyInvoker = null;

View File

@ -43,7 +43,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.REFERENCE_FILTER
public abstract class AbstractCluster implements Cluster {
private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker) {
// AbstractClusterInvoker<T> last = clusterInvoker;
AbstractClusterInvoker<T> last = buildInterceptorInvoker(new ClusterFilterInvoker<>(clusterInvoker));
if (Boolean.parseBoolean(ConfigurationUtils.getProperty(clusterInvoker.getDirectory().getConsumerUrl().getScopeModel(), CLUSTER_INTERCEPTOR_COMPATIBLE_KEY, "false"))) {
@ -72,7 +71,7 @@ public abstract class AbstractCluster implements Cluster {
protected abstract <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException;
static class ClusterFilterInvoker<T> extends AbstractClusterInvoker<T> {
private ClusterInvoker<T> filterInvoker;
private final ClusterInvoker<T> filterInvoker;
public ClusterFilterInvoker(AbstractClusterInvoker<T> invoker) {
List<FilterChainBuilder> builders = ScopeModelUtil.getApplicationModel(invoker.getUrl().getScopeModel()).getExtensionLoader(FilterChainBuilder.class).getActivateExtensions();
@ -191,9 +190,9 @@ public abstract class AbstractCluster implements Cluster {
@Deprecated
static class InterceptorInvokerNode<T> extends AbstractClusterInvoker<T> {
private AbstractClusterInvoker<T> clusterInvoker;
private ClusterInterceptor interceptor;
private AbstractClusterInvoker<T> next;
private final AbstractClusterInvoker<T> clusterInvoker;
private final ClusterInterceptor interceptor;
private final AbstractClusterInvoker<T> next;
public InterceptorInvokerNode(AbstractClusterInvoker<T> clusterInvoker,
ClusterInterceptor interceptor,

View File

@ -0,0 +1,225 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.support.wrapper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.url.component.ServiceConfigURL;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Cluster;
import org.apache.dubbo.rpc.cluster.ClusterInvoker;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.listener.ExporterChangeListener;
import org.apache.dubbo.rpc.listener.InjvmExporterListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
import static org.apache.dubbo.rpc.Constants.LOCAL_KEY;
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
import static org.apache.dubbo.rpc.Constants.SCOPE_KEY;
import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE;
import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
import static org.apache.dubbo.rpc.cluster.Constants.PEER_KEY;
/**
* A ClusterInvoker that selects between local and remote invokers at runtime.
*/
public class ScopeClusterInvoker<T> implements ClusterInvoker<T>, ExporterChangeListener {
private Protocol protocolSPI;
private final Directory<T> directory;
private final Invoker<T> invoker;
private final AtomicBoolean isExported;
private volatile Invoker<T> injvmInvoker;
private volatile InjvmExporterListener injvmExporterListener;
private boolean peerFlag;
private boolean injvmFlag;
private final Object createLock = new Object();
public ScopeClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
this.directory = directory;
this.invoker = invoker;
this.isExported = new AtomicBoolean(false);
init();
}
private void init() {
Boolean peer = (Boolean) getUrl().getAttribute(PEER_KEY);
String isInjvm = getUrl().getParameter(LOCAL_PROTOCOL);
if (peer != null && peer) {
peerFlag = true;
return;
}
if (injvmInvoker == null && LOCAL_PROTOCOL.equalsIgnoreCase(getRegistryUrl().getProtocol())) {
injvmInvoker = invoker;
isExported.compareAndSet(false, true);
injvmFlag = true;
return;
}
if (Boolean.TRUE.toString().equalsIgnoreCase(isInjvm) || LOCAL_KEY.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY))) {
injvmFlag = true;
} else if (isInjvm == null) {
injvmFlag = isNotRemoteOrGeneric();
}
protocolSPI = getUrl().getApplicationModel().getExtensionLoader(Protocol.class).getAdaptiveExtension();
injvmExporterListener = getUrl().getOrDefaultFrameworkModel().getBeanFactory().getBean(InjvmExporterListener.class);
injvmExporterListener.addExporterChangeListener(this, getUrl().getServiceKey());
}
@Override
public URL getUrl() {
return directory.getConsumerUrl();
}
@Override
public URL getRegistryUrl() {
return directory.getUrl();
}
@Override
public Directory<T> getDirectory() {
return directory;
}
@Override
public boolean isDestroyed() {
return directory.isDestroyed();
}
@Override
public boolean isAvailable() {
return isExported.get() || directory.isAvailable();
}
@Override
public void destroy() {
if (injvmExporterListener != null) {
injvmExporterListener.removeExporterChangeListener(this, getUrl().getServiceKey());
}
destroyInjvmInvoker();
this.invoker.destroy();
}
@Override
public Class<T> getInterface() {
return directory.getInterface();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
if (peerFlag) {
return invoker.invoke(invocation);
}
if (isInjvmExported()) {
return injvmInvoker.invoke(invocation);
}
return invoker.invoke(invocation);
}
private boolean isNotRemoteOrGeneric() {
return !SCOPE_REMOTE.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY)) &&
!getUrl().getParameter(GENERIC_KEY, false);
}
private boolean isInjvmExported() {
Boolean localInvoke = RpcContext.getServiceContext().getLocalInvoke();
boolean isExportedValue = isExported.get();
boolean local = (localInvoke != null && localInvoke);
// Determine whether this call is local
if (isExportedValue && local) {
return true;
}
// Determine whether this call is remote
if (localInvoke != null && !localInvoke) {
return false;
}
// When calling locally, determine whether it does not meet the requirements
if (!isExportedValue && (SCOPE_LOCAL.equalsIgnoreCase(getUrl().getParameter(SCOPE_KEY)) ||
Boolean.TRUE.toString().equalsIgnoreCase(getUrl().getParameter(LOCAL_PROTOCOL))|| local)) {
throw new RpcException("Local service has not been exposed yet!");
}
return isExportedValue && injvmFlag;
}
private void createInjvmInvoker() {
if (injvmInvoker == null) {
synchronized (createLock) {
if (injvmInvoker == null) {
URL url = new ServiceConfigURL(LOCAL_PROTOCOL, NetUtils.getLocalHost(), getUrl().getPort(), getInterface().getName(), getUrl().getParameters());
url = url.setScopeModel(getUrl().getScopeModel());
url = url.setServiceModel(getUrl().getServiceModel());
Invoker<?> invoker = protocolSPI.refer(getInterface(), url);
List<Invoker<?>> invokers = new ArrayList<>();
invokers.add(invoker);
injvmInvoker = Cluster.getCluster(url.getScopeModel(), Cluster.DEFAULT, false).join(new StaticDirectory(url, invokers), true);
}
}
}
}
@Override
public void onExporterChangeExport(Exporter<?> exporter) {
if (isExported.get()) {
return;
}
if (getUrl().getServiceKey().equals(exporter.getInvoker().getUrl().getServiceKey())
&& exporter.getInvoker().getUrl().getProtocol().equalsIgnoreCase(LOCAL_PROTOCOL)) {
createInjvmInvoker();
isExported.compareAndSet(false, true);
}
}
@Override
public void onExporterChangeUnExport(Exporter<?> exporter) {
if (getUrl().getServiceKey().equals(exporter.getInvoker().getUrl().getServiceKey())
&& exporter.getInvoker().getUrl().getProtocol().equalsIgnoreCase(LOCAL_PROTOCOL)) {
destroyInjvmInvoker();
isExported.compareAndSet(true, false);
}
}
private void destroyInjvmInvoker() {
if (injvmInvoker != null) {
injvmInvoker.destroy();
injvmInvoker = null;
}
}
public Invoker<?> getInvoker() {
return invoker;
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.support.wrapper;
import org.apache.dubbo.common.extension.Wrapper;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Cluster;
import org.apache.dubbo.rpc.cluster.Directory;
/**
* Introducing ScopeClusterInvoker section through Dubbo SPI mechanism
*/
@Wrapper(order = -1)
public class ScopeClusterWrapper implements Cluster {
private final Cluster cluster;
public ScopeClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
@Override
public <T> Invoker<T> join(Directory<T> directory, boolean buildFilterChain) throws RpcException {
return new ScopeClusterInvoker<>(directory,
this.cluster.join(directory, buildFilterChain));
}
public Cluster getCluster() {
return cluster;
}
}

View File

@ -1,4 +1,5 @@
mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
scope=org.apache.dubbo.rpc.cluster.support.wrapper.ScopeClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
@ -7,4 +8,4 @@ forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
zone-aware=org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster
zone-aware=org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster

View File

@ -0,0 +1,4 @@
attachment=org.apache.dubbo.rpc.cluster.router.condition.matcher.attachment.AttachmentConditionMatcherFactory
argument=org.apache.dubbo.rpc.cluster.router.condition.matcher.argument.ArgumentConditionMatcherFactory
param=org.apache.dubbo.rpc.cluster.router.condition.matcher.param.UrlParamConditionMatcherFactory

View File

@ -0,0 +1,2 @@
range=org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.range.RangeValuePattern
wildcard=org.apache.dubbo.rpc.cluster.router.condition.matcher.pattern.wildcard.WildcardValuePattern

View File

@ -2,5 +2,7 @@ tag=org.apache.dubbo.rpc.cluster.router.tag.TagStateRouterFactory
condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionStateRouterFactory
service=org.apache.dubbo.rpc.cluster.router.condition.config.ServiceStateRouterFactory
app=org.apache.dubbo.rpc.cluster.router.condition.config.AppStateRouterFactory
provider-app=org.apache.dubbo.rpc.cluster.router.condition.config.ProviderAppStateRouterFactory
script-app=org.apache.dubbo.rpc.cluster.router.script.config.AppScriptRouterFactory
mock=org.apache.dubbo.rpc.cluster.router.mock.MockStateRouterFactory
standard-mesh-rule=org.apache.dubbo.rpc.cluster.router.mesh.route.StandardMeshRuleRouterFactory

View File

@ -34,7 +34,7 @@ import java.util.Optional;
class ConfiguratorTest {
@Test
void test() throws Exception {
void test() {
Optional<List<Configurator>> emptyOptional = Configurator.toConfigurators(Collections.emptyList());
Assertions.assertEquals(Optional.empty(), emptyOptional);

View File

@ -59,7 +59,7 @@ class RouterChainTest {
void testBuildRouterChain() {
RouterChain<DemoService> routerChain = createRouterChanin();
Assertions.assertEquals(0, routerChain.getRouters().size());
Assertions.assertEquals(5, routerChain.getStateRouters().size());
Assertions.assertEquals(7, routerChain.getStateRouters().size());
}
private RouterChain<DemoService> createRouterChanin() {
@ -158,14 +158,16 @@ class RouterChainTest {
Assertions.assertTrue(result.contains(invoker5));
String snapshotLog =
"[ Parent (Input: 6) (Current Node Output: 6) (Chain Node Output: 1) ] Input: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103 -> Chain Node Output: localhost:9103...\n" +
"[ Parent (Input: 6) (Current Node Output: 6) (Chain Node Output: 1) ] Input: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103 -> Chain Node Output: localhost:9103...\n" +
" [ MockInvokersSelector (Input: 6) (Current Node Output: 5) (Chain Node Output: 1) Router message: invocation.need.mock not set. Return normal Invokers. ] Current Node Output: localhost:9103,localhost:9103,localhost:9103,localhost:9103,localhost:9103\n" +
" [ StandardMeshRuleRouter (Input: 5) (Current Node Output: 4) (Chain Node Output: 1) Router message: Match App: app Subset: isolation ] Current Node Output: localhost:9103,localhost:9103,localhost:9103,localhost:9103\n" +
" [ TagStateRouter (Input: 4) (Current Node Output: 3) (Chain Node Output: 1) Router message: Disable Tag Router. Reason: tagRouterRule is invalid or disabled ] Current Node Output: localhost:9103,localhost:9103,localhost:9103\n" +
" [ ServiceStateRouter (Input: 3) (Current Node Output: 3) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103,localhost:9103\n" +
" [ ConditionStateRouter (Input: 3) (Current Node Output: 2) (Chain Node Output: 2) Router message: Match return. ] Current Node Output: localhost:9103,localhost:9103\n" +
" [ AppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103\n" +
" [ ConditionStateRouter (Input: 2) (Current Node Output: 1) (Chain Node Output: 1) Router message: Match return. ] Current Node Output: localhost:9103";
" [ ProviderAppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: Directly return. Reason: Invokers from previous router is empty or conditionRouters is empty. ] Current Node Output: localhost:9103,localhost:9103\n" +
" [ AppStateRouter (Input: 2) (Current Node Output: 2) (Chain Node Output: 1) Router message: null ] Current Node Output: localhost:9103,localhost:9103\n" +
" [ ConditionStateRouter (Input: 2) (Current Node Output: 1) (Chain Node Output: 1) Router message: Match return. ] Current Node Output: localhost:9103\n" +
" [ AppScriptStateRouter (Input: 1) (Current Node Output: 1) (Chain Node Output: 1) Router message: Directly return from script router. Reason: Invokers from previous router is empty or script is not enabled. Script rule is: null ] Current Node Output: localhost:9103";
String[] snapshot = routerSnapshotSwitcher.cloneSnapshot();
Assertions.assertTrue(snapshot[0].contains(snapshotLog));

View File

@ -20,13 +20,19 @@ import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.rpc.cluster.configurator.absent.AbsentConfigurator;
import org.apache.dubbo.rpc.cluster.configurator.consts.UrlConstant;
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConditionMatch;
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ParamMatch;
import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.match.StringMatch;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
/**
* OverrideConfiguratorTest
*/
@ -80,6 +86,7 @@ class OverrideConfiguratorTest {
params.put("configVersion", "2.7");
params.put("application", "foo");
params.put("timeout", "10000");
consumerConfiguratorUrl = consumerConfiguratorUrl.addParameters(params);
OverrideConfigurator configurator = new OverrideConfigurator(consumerConfiguratorUrl);
@ -114,4 +121,76 @@ class OverrideConfiguratorTest {
}
// Test the version after 2.7
@Test
void testOverrideForVersion3() {
// match
{
String consumerUrlV3 = "dubbo://172.24.160.179/com.foo.BarService?match_key=value&application=foo&side=consumer&timeout=100";
URL consumerConfiguratorUrl = URL.valueOf("override://0.0.0.0/com.foo.BarService");
Map<String, String> params = new HashMap<>();
params.put("side", "consumer");
params.put("configVersion", "v3.0");
params.put("application", "foo");
params.put("timeout", "10000");
ConditionMatch matcher = new ConditionMatch();
ParamMatch paramMatch = new ParamMatch();
paramMatch.setKey("match_key");
StringMatch stringMatch = new StringMatch();
stringMatch.setExact("value");
paramMatch.setValue(stringMatch);
matcher.setParam(Arrays.asList(paramMatch));
consumerConfiguratorUrl = consumerConfiguratorUrl.putAttribute(MATCH_CONDITION, matcher);
consumerConfiguratorUrl = consumerConfiguratorUrl.addParameters(params);
OverrideConfigurator configurator = new OverrideConfigurator(consumerConfiguratorUrl);
// Meet the configured conditions:
// same side
// The port of configuratorUrl is 0
// The host of configuratorUrl is 0.0.0.0 or the local address is the same as consumerUrlV27
// same appName
URL originalURL = URL.valueOf(consumerUrlV3);
Assertions.assertEquals("100", originalURL.getParameter("timeout"));
URL url = configurator.configure(originalURL);
Assertions.assertEquals("10000", url.getParameter("timeout"));
}
// mismatch
{
String consumerUrlV3 = "dubbo://172.24.160.179/com.foo.BarService?match_key=value&application=foo&side=consumer&timeout=100";
URL consumerConfiguratorUrl = URL.valueOf("override://0.0.0.0/com.foo.BarService");
Map<String, String> params = new HashMap<>();
params.put("side", "consumer");
params.put("configVersion", "v3.0");
params.put("application", "foo");
params.put("timeout", "10000");
ConditionMatch matcher = new ConditionMatch();
ParamMatch paramMatch = new ParamMatch();
paramMatch.setKey("match_key");
StringMatch stringMatch = new StringMatch();
stringMatch.setExact("not_match_value");
paramMatch.setValue(stringMatch);
matcher.setParam(Arrays.asList(paramMatch));
consumerConfiguratorUrl = consumerConfiguratorUrl.putAttribute(MATCH_CONDITION, matcher);
consumerConfiguratorUrl = consumerConfiguratorUrl.addParameters(params);
OverrideConfigurator configurator = new OverrideConfigurator(consumerConfiguratorUrl);
// Meet the configured conditions:
// same side
// The port of configuratorUrl is 0
// The host of configuratorUrl is 0.0.0.0 or the local address is the same as consumerUrlV27
// same appName
URL originalURL = URL.valueOf(consumerUrlV3);
Assertions.assertEquals("100", originalURL.getParameter("timeout"));
URL url = configurator.configure(originalURL);
Assertions.assertEquals("100", url.getParameter("timeout"));
}
}
}

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.cluster.configurator.parser;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConditionMatch;
import org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig;
import org.junit.jupiter.api.Assertions;
@ -33,6 +34,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.LOADBALANCE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.OVERRIDE_PROVIDERS_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.WEIGHT_KEY;
import static org.apache.dubbo.rpc.cluster.configurator.parser.model.ConfiguratorConfig.MATCH_CONDITION;
/**
*
@ -165,6 +167,75 @@ class ConfigParserTest {
}
}
@Test
void parseProviderConfigurationV3() throws IOException {
try (InputStream yamlStream = this.getClass().getResourceAsStream("/ConfiguratorV3.yml")) {
List<URL> urls = ConfigParser.parseConfigurators(streamToString(yamlStream));
Assertions.assertNotNull(urls);
Assertions.assertEquals(1, urls.size());
URL url = urls.get(0);
Assertions.assertEquals("0.0.0.0", url.getAddress());
Assertions.assertEquals("*", url.getServiceInterface());
Assertions.assertEquals(200, url.getParameter(WEIGHT_KEY, 0));
Assertions.assertEquals("demo-provider", url.getApplication());
URL matchURL1 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value1");
URL matchURL2 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService2?match_key1=value1");
URL notMatchURL1 = URL.valueOf("dubbo://10.0.0.2:20880/DemoService?match_key1=value1");// address not match
URL notMatchURL2 = URL.valueOf("dubbo://10.0.0.1:20880/DemoServiceNotMatch?match_key1=value1");// service not match
URL notMatchURL3 = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
Assertions.assertTrue(matcher.isMatch(matchURL1));
Assertions.assertTrue(matcher.isMatch(matchURL2));
Assertions.assertFalse(matcher.isMatch(notMatchURL1));
Assertions.assertFalse(matcher.isMatch(notMatchURL2));
Assertions.assertFalse(matcher.isMatch(notMatchURL3));
}
}
@Test
void parseProviderConfigurationV3Compatibility() throws IOException {
try (InputStream yamlStream = this.getClass().getResourceAsStream("/ConfiguratorV3Compatibility.yml")) {
List<URL> urls = ConfigParser.parseConfigurators(streamToString(yamlStream));
Assertions.assertNotNull(urls);
Assertions.assertEquals(1, urls.size());
URL url = urls.get(0);
Assertions.assertEquals("10.0.0.1:20880", url.getAddress());
Assertions.assertEquals("DemoService", url.getServiceInterface());
Assertions.assertEquals(200, url.getParameter(WEIGHT_KEY, 0));
Assertions.assertEquals("demo-provider", url.getApplication());
URL matchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value1");
URL notMatchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
Assertions.assertTrue(matcher.isMatch(matchURL));
Assertions.assertFalse(matcher.isMatch(notMatchURL));
}
}
@Test
void parseProviderConfigurationV3Conflict() throws IOException {
try (InputStream yamlStream = this.getClass().getResourceAsStream("/ConfiguratorV3Duplicate.yml")) {
List<URL> urls = ConfigParser.parseConfigurators(streamToString(yamlStream));
Assertions.assertNotNull(urls);
Assertions.assertEquals(1, urls.size());
URL url = urls.get(0);
Assertions.assertEquals("10.0.0.1:20880", url.getAddress());
Assertions.assertEquals("DemoService", url.getServiceInterface());
Assertions.assertEquals(200, url.getParameter(WEIGHT_KEY, 0));
Assertions.assertEquals("demo-provider", url.getApplication());
URL matchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value1");
URL notMatchURL = URL.valueOf("dubbo://10.0.0.1:20880/DemoService?match_key1=value_not_match");// key not match
ConditionMatch matcher = (ConditionMatch) url.getAttribute(MATCH_CONDITION);
Assertions.assertTrue(matcher.isMatch(matchURL));
Assertions.assertFalse(matcher.isMatch(notMatchURL));
}
}
@Test
void parseURLJsonArrayCompatible() {

View File

@ -19,6 +19,7 @@ package org.apache.dubbo.rpc.cluster.directory;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.cluster.router.MockInvoker;
@ -26,6 +27,7 @@ import org.apache.dubbo.rpc.cluster.router.condition.ConditionStateRouterFactory
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@ -60,6 +62,7 @@ class StaticDirectoryTest {
List<Invoker<String>> filteredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation(), false, new Holder<>());
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
StaticDirectory<String> staticDirectory = new StaticDirectory<>(filteredInvokers);
boolean isAvailable = staticDirectory.isAvailable();
Assertions.assertTrue(!isAvailable);

View File

@ -43,7 +43,7 @@ class DefaultFilterChainBuilderTest {
urlWithoutFilter = urlWithoutFilter.setScopeModel(ApplicationModel.defaultModel());
AbstractInvoker<DemoService> invokerWithoutFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithoutFilter) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
protected Result doInvoke(Invocation invocation) {
return null;
}
};
@ -58,7 +58,7 @@ class DefaultFilterChainBuilderTest {
urlWithFilter = urlWithFilter.setScopeModel(ApplicationModel.defaultModel());
AbstractInvoker<DemoService> invokerWithFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithFilter) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
protected Result doInvoke(Invocation invocation) {
return null;
}
};
@ -78,7 +78,7 @@ class DefaultFilterChainBuilderTest {
urlWithoutFilter = urlWithoutFilter.setScopeModel(ApplicationModel.defaultModel());
AbstractInvoker<DemoService> invokerWithoutFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithoutFilter) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
protected Result doInvoke(Invocation invocation) {
return null;
}
};
@ -93,7 +93,7 @@ class DefaultFilterChainBuilderTest {
urlWithFilter = urlWithFilter.setScopeModel(ApplicationModel.defaultModel());
AbstractInvoker<DemoService> invokerWithFilter = new AbstractInvoker<DemoService>(DemoService.class, urlWithFilter) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
protected Result doInvoke(Invocation invocation) {
return null;
}
};

View File

@ -23,11 +23,17 @@ import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.model.ServiceModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class RouterSnapshotFilterTest {
@BeforeAll
static void setUp() {
RpcContext.getServiceContext().setNeedPrintRouterSnapshot(false);
}
@Test
void test() {
FrameworkModel frameworkModel = new FrameworkModel();

View File

@ -369,4 +369,124 @@ class ConditionStateRouterTest {
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(0, fileredInvokers.size());
}
@Test
void testRoute_Attachments() {
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[foo] = a " + " => " + " host = 1.2.3.4").addParameter(FORCE_KEY, String.valueOf(true)));
List<Invoker<String>> originInvokers = new ArrayList<>();
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService?region=hangzhou"));
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
RpcInvocation invocation = new RpcInvocation();
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
invocation.setAttachment("foo", "a");
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(0, fileredInvokers.size());
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments = a " + " => " + " host = 1.2.3.4").addParameter(FORCE_KEY, String.valueOf(true)));
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[foo] = a " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(1, fileredInvokers.size());
}
@Test
void testRoute_Range_Pattern() {
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = 1~100 " + " => " + " region=hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
List<Invoker<String>> originInvokers = new ArrayList<>();
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService?region=hangzhou"));
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
RpcInvocation invocation = new RpcInvocation();
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
invocation.setAttachment("user_id", "80");
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(1, fileredInvokers.size());
invocation.setAttachment("user_id", "101");
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = ~100 " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
invocation.setAttachment("user_id", "1");
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(1, fileredInvokers.size());
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = ~100 " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
invocation.setAttachment("user_id", "101");
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("attachments[user_id] = ~100 " + " => " + " region = hangzhou").addParameter(FORCE_KEY, String.valueOf(true)));
fileredInvokers = router.route(invokers.clone(), URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService"), invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
}
@Test
void testRoute_Key_Not_Exist() {
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("when_key=a " + " => " + " not_exist_then_key = any_value").addParameter(FORCE_KEY, String.valueOf(true)));
List<Invoker<String>> originInvokers = new ArrayList<>();
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService?then_key=a"));
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
URL consumer = URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService?when_key=a");
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), consumer, null, false, new Holder<>());
Assertions.assertEquals(0, fileredInvokers.size());
router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("not_exist_when_key=a " + " => " + " then_key = a").addParameter(FORCE_KEY, String.valueOf(true)));
fileredInvokers = router.route(invokers, consumer, null, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
}
@Test
void testRoute_Multiple_Conditions() {
List<Invoker<String>> originInvokers = new ArrayList<>();
Invoker<String> invoker1 = new MockInvoker<>(URL.valueOf("dubbo://10.20.3.3:20880/com.foo.BarService"));
Invoker<String> invoker2 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
Invoker<String> invoker3 = new MockInvoker<>(URL.valueOf("dubbo://" + LOCAL_HOST + ":20880/com.foo.BarService"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
RpcInvocation invocation = new RpcInvocation();
String p = "a";
invocation.setArguments(new Object[]{p});
// all conditions match
URL consumer1 = URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService?application=consumer_app");
StateRouter router = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("application=consumer_app&arguments[0]=a" + " => " + " host = " + LOCAL_HOST).addParameter(FORCE_KEY, String.valueOf(true)));
List<Invoker<String>> fileredInvokers = router.route(invokers.clone(), consumer1, invocation, false, new Holder<>());
Assertions.assertEquals(2, fileredInvokers.size());
// one of the conditions does not match
URL consume2 = URL.valueOf("consumer://" + LOCAL_HOST + "/com.foo.BarService?application=another_consumer_app");
StateRouter router2 = new ConditionStateRouterFactory().getRouter(String.class, getRouteUrl("application=consumer_app&arguments[0]=a" + " => " + " host = " + LOCAL_HOST).addParameter(FORCE_KEY, String.valueOf(true)));
fileredInvokers = router2.route(invokers.clone(), consume2, invocation, false, new Holder<>());
Assertions.assertEquals(3, fileredInvokers.size());
}
}

View File

@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.condition.config;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
import org.apache.dubbo.rpc.cluster.router.MockInvoker;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
public class ProviderAppConditionStateRouterTest {
private static final String LOCAL_HOST = "127.0.0.1";
private static final String RULE_SUFFIX = ".condition-router";
private static GovernanceRuleRepository ruleRepository;
private URL url = URL.valueOf("consumer://1.1.1.1/com.foo.BarService");
private String rawRule = "---\n" +
"configVersion: v3.0\n" +
"scope: application\n" +
"force: true\n" +
"runtime: false\n" +
"enabled: true\n" +
"priority: 1\n" +
"key: demo-provider\n" +
"conditions:\n" +
"- method=sayHello => region=hangzhou\n" +
"...";
@BeforeAll
public static void setUpBeforeClass() throws Exception {
ruleRepository = Mockito.mock(GovernanceRuleRepository.class);
}
@Test
void test() {
ProviderAppStateRouter<String> router = new ProviderAppStateRouter<>(url);
router = Mockito.spy(router);
Mockito.when(router.getRuleRepository()).thenReturn(ruleRepository);
Mockito.when(ruleRepository.getRule("demo-provider" + RULE_SUFFIX, DynamicConfiguration.DEFAULT_GROUP)).thenReturn(rawRule);
// Mockito.when(ruleRepository.addListener()).thenReturn();
BitList<Invoker<String>> invokers = getInvokers();
router.notify(invokers);
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("sayHello");
List<Invoker<String>> result = router.route(invokers.clone(), url, invocation, false, new Holder<>());
Assertions.assertEquals(1, result.size());
invocation.setMethodName("sayHi");
result = router.route(invokers.clone(), url, invocation, false, new Holder<>());
Assertions.assertEquals(3, result.size());
}
private BitList<Invoker<String>> getInvokers() {
List<Invoker<String>> originInvokers = new ArrayList<Invoker<String>>();
Invoker<String> invoker1 = new MockInvoker<String>(URL.valueOf(
"dubbo://10.20.3.3:20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
Invoker<String> invoker2 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider&region=hangzhou"));
Invoker<String> invoker3 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
return invokers;
}
}

View File

@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.router.file;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.AppResponse;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
@ -30,6 +31,7 @@ import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@ -59,6 +61,7 @@ class FileRouterEngineTest {
@BeforeAll
public static void setUpBeforeClass() throws Exception {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
System.setProperty(ENABLE_CONNECTIVITY_VALIDATION, "false");
}

View File

@ -34,7 +34,6 @@ import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -51,7 +50,11 @@ class MeshRuleCacheTest {
@Test
void containMapKeyValue() {
URL url = mock(URL.class);
when(url.getServiceKey()).thenReturn("test");
when(url.getOriginalServiceParameter("test", "key1")).thenReturn("value1");
when(url.getOriginalServiceParameter("test", "key2")).thenReturn("value2");
when(url.getOriginalServiceParameter("test", "key3")).thenReturn("value3");
when(url.getOriginalServiceParameter("test", "key4")).thenReturn("value4");
Map<String, String> originMap = new HashMap<>();
@ -64,14 +67,10 @@ class MeshRuleCacheTest {
inputMap.put("key1", "value1");
inputMap.put("key2", "value2");
assertTrue(MeshRuleCache.containMapKeyValue(originMap, inputMap));
assertTrue(MeshRuleCache.isLabelMatch(url, "test", inputMap));
inputMap.put("key4", "value4");
assertFalse(MeshRuleCache.containMapKeyValue(originMap, inputMap));
assertTrue(MeshRuleCache.containMapKeyValue(originMap, null));
assertTrue(MeshRuleCache.containMapKeyValue(originMap, new HashMap<>()));
assertTrue(MeshRuleCache.isLabelMatch(url, "test", inputMap));
}

View File

@ -0,0 +1,100 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.router.script.config;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.DynamicConfiguration;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository;
import org.apache.dubbo.rpc.cluster.router.MockInvoker;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY;
@DisabledForJreRange(min = JRE.JAVA_16)
public class AppScriptStateRouterTest {
private static final String LOCAL_HOST = "127.0.0.1";
private static final String RULE_SUFFIX = ".script-router";
private static GovernanceRuleRepository ruleRepository;
private URL url = URL.valueOf("dubbo://1.1.1.1/com.foo.BarService");
private String rawRule = "---\n" +
"configVersion: v3.0\n" +
"key: demo-provider\n" +
"type: javascript\n" +
"script: |\n" +
" (function route(invokers,invocation,context) {\n" +
" var result = new java.util.ArrayList(invokers.size());\n" +
" for (i = 0; i < invokers.size(); i ++) {\n" +
" if (\"10.20.3.3\".equals(invokers.get(i).getUrl().getHost())) {\n" +
" result.add(invokers.get(i));\n" +
" }\n" +
" }\n" +
" return result;\n" +
" } (invokers, invocation, context)); // 表示立即执行方法\n" +
"...";
@BeforeAll
public static void setUpBeforeClass() throws Exception {
ruleRepository = Mockito.mock(GovernanceRuleRepository.class);
}
@Test
void testConfigScriptRoute() {
AppScriptStateRouter<String> router = new AppScriptStateRouter<>(url);
router = Mockito.spy(router);
Mockito.when(router.getRuleRepository()).thenReturn(ruleRepository);
Mockito.when(ruleRepository.getRule("demo-provider" + RULE_SUFFIX, DynamicConfiguration.DEFAULT_GROUP)).thenReturn(rawRule);
// Mockito.when(ruleRepository.addListener()).thenReturn();
BitList<Invoker<String>> invokers = getInvokers();
router.notify(invokers);
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("sayHello");
List<Invoker<String>> result = router.route(invokers.clone(), url, invocation, false, new Holder<>());
Assertions.assertEquals(1, result.size());
}
private BitList<Invoker<String>> getInvokers() {
List<Invoker<String>> originInvokers = new ArrayList<Invoker<String>>();
Invoker<String> invoker1 = new MockInvoker<String>(URL.valueOf(
"dubbo://10.20.3.3:20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
Invoker<String> invoker2 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
Invoker<String> invoker3 = new MockInvoker<String>(URL.valueOf("dubbo://" + LOCAL_HOST
+ ":20880/com.foo.BarService?" + REMOTE_APPLICATION_KEY + "=demo-provider"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
return invokers;
}
}

View File

@ -94,6 +94,35 @@ class TagStateRouterTest {
Assertions.assertEquals(invoker1, filteredInvokers.get(0));
}
@Test
void testTagRouteWithDynamicRuleV3() {
TagStateRouter router = (TagStateRouter) new TagStateRouterFactory().getRouter(TagRouterRule.class, url);
router = Mockito.spy(router);
List<Invoker<String>> originInvokers = new ArrayList<>();
URL url1 = URL.valueOf("test://127.0.0.1:7777/DemoInterface?application=foo&dubbo.tag=tag2&match_key=value").setScopeModel(moduleModel);
URL url2 = URL.valueOf("test://127.0.0.1:7778/DemoInterface?application=foo&match_key=value").setScopeModel(moduleModel);
URL url3 = URL.valueOf("test://127.0.0.1:7779/DemoInterface?application=foo").setScopeModel(moduleModel);
Invoker<String> invoker1 = new MockInvoker<>(url1, true);
Invoker<String> invoker2 = new MockInvoker<>(url2, true);
Invoker<String> invoker3 = new MockInvoker<>(url3, true);
originInvokers.add(invoker1);
originInvokers.add(invoker2);
originInvokers.add(invoker3);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
RpcInvocation invocation = new RpcInvocation();
invocation.setAttachment(TAG_KEY, "tag2");
TagRouterRule rule = getTagRule();
Mockito.when(router.getInvokers()).thenReturn(invokers);
rule.init(router);
router.setTagRouterRule(rule);
List<Invoker<String>> filteredInvokers = router.route(invokers, invokers.get(0).getUrl(), invocation, false, new Holder<>());
Assertions.assertEquals(2, filteredInvokers.size());
// Assertions.(invoker1, filteredInvokers.get(0));
}
/**
* TagRouterRule parse test when the tags addresses is null
*
@ -122,6 +151,9 @@ class TagStateRouterTest {
"...";
TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig);
TagStateRouter<?> router = Mockito.mock(TagStateRouter.class);
Mockito.when(router.getInvokers()).thenReturn(BitList.emptyList());
tagRouterRule.init(router);
// assert tags
assert tagRouterRule.getKey().equals("demo-provider");
@ -138,4 +170,99 @@ class TagStateRouterTest {
assert tagRouterRule.getTagnameToAddresses().get("tag4") == null;
assert tagRouterRule.getAddresses().size() == 1;
}
@Test
void tagRouterRuleParseTestV3() {
String tagRouterRuleConfig = "---\n" +
"configVersion: v3.0\n" +
"force: false\n" +
"runtime: true\n" +
"enabled: true\n" +
"priority: 1\n" +
"key: demo-provider\n" +
"tags:\n" +
" - name: tag1\n" +
" match:\n" +
" - key: match_key1\n" +
" value:\n" +
" exact: value1\n" +
" - name: tag2\n" +
" addresses:\n" +
" - \"10.20.3.3:20880\"\n" +
" - \"10.20.3.4:20880\"\n" +
" match:\n" +
" - key: match_key2\n" +
" value:\n" +
" exact: value2\n" +
" - name: tag3\n" +
" match:\n" +
" - key: match_key2\n" +
" value:\n" +
" exact: value2\n" +
" - name: tag4\n" +
" match:\n" +
" - key: not_exist\n" +
" value:\n" +
" exact: not_exist\n" +
" - name: tag5\n" +
" match:\n" +
" - key: match_key2\n" +
" value:\n" +
" wildcard: \"*\"\n" +
"...";
TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig);
TagStateRouter<String> router = Mockito.mock(TagStateRouter.class);
Mockito.when(router.getInvokers()).thenReturn(getInvokers());
tagRouterRule.init(router);
// assert tags
assert tagRouterRule.getKey().equals("demo-provider");
assert tagRouterRule.getPriority() == 1;
assert tagRouterRule.getTagNames().contains("tag1");
assert tagRouterRule.getTagNames().contains("tag2");
assert tagRouterRule.getTagNames().contains("tag3");
assert tagRouterRule.getTagNames().contains("tag4");
// assert addresses
assert tagRouterRule.getAddresses().size() == 2;
assert tagRouterRule.getAddresses().contains("10.20.3.3:20880");
assert tagRouterRule.getTagnameToAddresses().get("tag1").size() == 2;
assert tagRouterRule.getTagnameToAddresses().get("tag2").size() == 2;
assert tagRouterRule.getTagnameToAddresses().get("tag3").size() == 1;
assert tagRouterRule.getTagnameToAddresses().get("tag5").size() == 1;
assert tagRouterRule.getTagnameToAddresses().get("tag4") == null;
}
public BitList<Invoker<String>> getInvokers() {
List<Invoker<String>> originInvokers = new ArrayList<Invoker<String>>();
Invoker<String> invoker1 = new MockInvoker<String>(URL.valueOf(
"dubbo://10.20.3.3:20880/com.foo.BarService?match_key1=value1&match_key2=value2"));
Invoker<String> invoker2 = new MockInvoker<String>(URL.valueOf("dubbo://10.20.3.4:20880/com.foo.BarService?match_key1=value1"));
originInvokers.add(invoker1);
originInvokers.add(invoker2);
BitList<Invoker<String>> invokers = new BitList<>(originInvokers);
return invokers;
}
private TagRouterRule getTagRule() {
String tagRouterRuleConfig = "---\n" +
"configVersion: v3.0\n" +
"force: false\n" +
"runtime: true\n" +
"enabled: true\n" +
"priority: 1\n" +
"key: demo-provider\n" +
"tags:\n" +
" - name: tag2\n" +
" match:\n" +
" - key: match_key\n" +
" value:\n" +
" exact: value\n" +
"...";
TagRouterRule tagRouterRule = TagRuleParser.parse(tagRouterRuleConfig);
return tagRouterRule;
}
}

View File

@ -16,18 +16,11 @@
*/
package org.apache.dubbo.rpc.cluster.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
@ -41,6 +34,8 @@ import org.apache.dubbo.rpc.cluster.filter.DemoService;
import org.apache.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance;
import org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance;
import org.apache.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@ -50,6 +45,14 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
import static org.apache.dubbo.common.constants.CommonConstants.ENABLE_CONNECTIVITY_VALIDATION;
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
@ -104,6 +107,7 @@ class AbstractClusterInvokerTest {
@SuppressWarnings({"unchecked"})
@BeforeEach
public void setUp() throws Exception {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
Map<String, Object> attributes = new HashMap<>();
attributes.put("application", "abstractClusterInvokerTest");
url = url.putAttribute(REFER_KEY, attributes);
@ -191,7 +195,7 @@ class AbstractClusterInvokerTest {
}
@Test
void testSelect_Invokersize0() throws Exception {
void testSelect_Invokersize0() {
LoadBalance l = cluster.initLoadBalance(invokers, invocation);
Assertions.assertNotNull(l,"cluster.initLoadBalance returns null!");
{
@ -225,7 +229,7 @@ class AbstractClusterInvokerTest {
}
@Test
void testSelect_Invokersize1() throws Exception {
void testSelect_Invokersize1() {
invokers.clear();
invokers.add(invoker1);
LoadBalance l = cluster.initLoadBalance(invokers, invocation);
@ -235,7 +239,7 @@ class AbstractClusterInvokerTest {
}
@Test
void testSelect_Invokersize2AndselectNotNull() throws Exception {
void testSelect_Invokersize2AndselectNotNull() {
invokers.clear();
invokers.add(invoker2);
invokers.add(invoker4);
@ -256,7 +260,7 @@ class AbstractClusterInvokerTest {
}
@Test
void testSelect_multiInvokers() throws Exception {
void testSelect_multiInvokers() {
testSelect_multiInvokers(RoundRobinLoadBalance.NAME);
testSelect_multiInvokers(LeastActiveLoadBalance.NAME);
testSelect_multiInvokers(RandomLoadBalance.NAME);
@ -409,7 +413,7 @@ class AbstractClusterInvokerTest {
}
public void testSelect_multiInvokers(String lbname) throws Exception {
public void testSelect_multiInvokers(String lbname) {
int min = 100, max = 500;
Double d = (Math.random() * (max - min + 1) + min);

View File

@ -55,7 +55,7 @@ class ClusterUtilsTest {
}
@Test
void testMergeUrl() throws Exception {
void testMergeUrl() {
URL providerURL = URL.valueOf("dubbo://localhost:55555");
providerURL = providerURL.setPath("path")
.setUsername("username")

View File

@ -19,6 +19,7 @@ package org.apache.dubbo.rpc.cluster.support;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
@ -28,6 +29,7 @@ import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@ -70,6 +72,7 @@ class ConnectivityValidationTest {
@BeforeEach
public void setup() {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
invoker1 = Mockito.mock(Invoker.class);
invoker2 = Mockito.mock(Invoker.class);
invoker3 = Mockito.mock(Invoker.class);

View File

@ -16,11 +16,8 @@
*/
package org.apache.dubbo.rpc.cluster.support;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.AppResponse;
import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Invocation;
@ -33,10 +30,15 @@ import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.SingleRouterChain;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.protocol.AbstractInvoker;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
@ -65,7 +67,7 @@ class FailoverClusterInvokerTest {
@BeforeEach
public void setUp() throws Exception {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
dic = mock(Directory.class);
given(dic.getUrl()).willReturn(url);

View File

@ -57,7 +57,7 @@ class DefaultProviderURLMergeProcessorTest {
}
@Test
void testMergeUrl() throws Exception {
void testMergeUrl() {
URL providerURL = URL.valueOf("dubbo://localhost:55555");
providerURL = providerURL.setPath("path")
.setUsername("username")

View File

@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.support.wrapper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
@ -28,6 +29,7 @@ import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.support.MockProtocol;
import org.junit.jupiter.api.Assertions;
@ -49,6 +51,7 @@ class MockClusterInvokerTest {
@BeforeEach
public void beforeMethod() {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
invokers.clear();
}

View File

@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.cluster.support.wrapper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ProxyFactory;
@ -28,6 +29,7 @@ import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -46,6 +48,7 @@ class MockProviderRpcExceptionTest {
@BeforeEach
public void beforeMethod() {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
invokers.clear();
}

View File

@ -0,0 +1,282 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.dubbo.rpc.cluster.support.wrapper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.metrics.event.GlobalMetricsEventMulticaster;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Protocol;
import org.apache.dubbo.rpc.ProxyFactory;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.directory.StaticDirectory;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.apache.dubbo.common.constants.CommonConstants.EXPORTER_LISTENER_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL;
import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.SCOPE_KEY;
class ScopeClusterInvokerTest {
private final List<Invoker<DemoService>> invokers = new ArrayList<>();
private final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private final ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private final List<Exporter<?>> exporters = new ArrayList<>();
@BeforeEach
void beforeMonth() {
ApplicationModel.defaultModel().getBeanFactory().registerBean(GlobalMetricsEventMulticaster.class);
}
@AfterEach
void after() throws Exception {
for (Exporter<?> exporter : exporters) {
exporter.unexport();
}
exporters.clear();
for (Invoker<DemoService> invoker : invokers) {
invoker.destroy();
}
invokers.clear();
}
@Test
void testScopeNull_RemoteInvoke() {
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
url = url.addParameter(REFER_KEY,
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
Invoker<DemoService> cluster = getClusterInvoker(url);
invokers.add(cluster);
//Configured with mock
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("doSomething1");
Result ret = cluster.invoke(invocation);
Assertions.assertEquals("doSomething1", ret.getValue());
}
@Test
void testScopeNull_LocalInvoke() {
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
url = url.addParameter(REFER_KEY,
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule()) ;
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
exporters.add(exporter);
Invoker<DemoService> cluster = getClusterInvoker(url);
invokers.add(cluster);
//Configured with mock
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("doSomething2");
invocation.setParameterTypes(new Class[]{});
Result ret = cluster.invoke(invocation);
Assertions.assertEquals("doSomething2", ret.getValue());
}
@Test
void testScopeRemoteInvoke() {
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
url = url.addParameter(REFER_KEY,
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
url = url.addParameter(SCOPE_KEY, "remote");
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
exporters.add(exporter);
Invoker<DemoService> cluster = getClusterInvoker(url);
invokers.add(cluster);
//Configured with mock
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("doSomething3");
invocation.setParameterTypes(new Class[]{});
Result ret = cluster.invoke(invocation);
Assertions.assertEquals("doSomething3", ret.getValue());
}
@Test
void testScopeLocalInvoke() {
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
url = url.addParameter(REFER_KEY,
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
url = url.addParameter(SCOPE_KEY, SCOPE_LOCAL);
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
.addParameter(INTERFACE_KEY, DemoService.class.getName());
injvmUrl = injvmUrl.addParameter(EXPORTER_LISTENER_KEY, LOCAL_PROTOCOL).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
exporters.add(exporter);
Invoker<DemoService> cluster = getClusterInvoker(url);
invokers.add(cluster);
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("doSomething4");
invocation.setParameterTypes(new Class[]{});
Assertions.assertTrue(cluster.isAvailable(), "");
Result ret = cluster.invoke(invocation);
Assertions.assertEquals("doSomething4", ret.getValue());
}
@Test
void testInjvmRefer() {
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, DemoService.class.getName());
url = url.addParameter(REFER_KEY,
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
exporters.add(exporter);
Invoker<DemoService> cluster = getClusterInvoker(url);
invokers.add(cluster);
//Configured with mock
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("doSomething4");
invocation.setParameterTypes(new Class[]{});
Assertions.assertTrue(cluster.isAvailable(), "");
Result ret = cluster.invoke(invocation);
Assertions.assertEquals("doSomething4", ret.getValue());
}
@Test
void testListenUnExport() throws NoSuchFieldException, IllegalAccessException {
URL url = URL.valueOf("remote://1.2.3.4/" + DemoService.class.getName());
url = url.addParameter(REFER_KEY,
URL.encode(PATH_KEY + "=" + DemoService.class.getName()));
url = url.addParameter(SCOPE_KEY, "local");
url = url.setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
URL injvmUrl = URL.valueOf("injvm://127.0.0.1/TestService")
.addParameter(INTERFACE_KEY, DemoService.class.getName()).setScopeModel(ApplicationModel.defaultModel().getDefaultModule());
Exporter<?> exporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, injvmUrl));
Invoker<DemoService> cluster = getClusterInvoker(url);
invokers.add(cluster);
exporter.unexport();
Assertions.assertTrue(cluster instanceof ScopeClusterInvoker);
Field injvmInvoker = cluster.getClass().getDeclaredField("injvmInvoker");
injvmInvoker.setAccessible(true);
Assertions.assertNull(injvmInvoker.get(cluster));
Field isExported = cluster.getClass().getDeclaredField("isExported");
isExported.setAccessible(true);
Assertions.assertFalse(((AtomicBoolean) isExported.get(cluster)).get());
}
private Invoker<DemoService> getClusterInvoker(URL url) {
final URL durl = url.addParameter("proxy", "jdk");
invokers.clear();
ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension("jdk");
Invoker<DemoService> invoker1 = proxy.getInvoker(new DemoServiceImpl(), DemoService.class, durl);
invokers.add(invoker1);
StaticDirectory<DemoService> dic = new StaticDirectory<>(durl, invokers, null);
dic.buildRouterChain();
AbstractClusterInvoker<DemoService> cluster = new AbstractClusterInvoker(dic) {
@Override
protected Result doInvoke(Invocation invocation, List invokers, LoadBalance loadbalance)
throws RpcException {
if (durl.getParameter("invoke_return_error", false)) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "test rpc exception");
} else {
return ((Invoker<?>) invokers.get(0)).invoke(invocation);
}
}
};
return new ScopeClusterInvoker<>(dic, cluster);
}
public static interface DemoService {
String doSomething1();
String doSomething2();
String doSomething3();
String doSomething4();
}
public static class DemoServiceImpl implements DemoService {
@Override
public String doSomething1() {
return "doSomething1";
}
@Override
public String doSomething2() {
return "doSomething2";
}
@Override
public String doSomething3() {
return "doSomething3";
}
@Override
public String doSomething4() {
return "doSomething4";
}
}
}

View File

@ -29,4 +29,4 @@ configs:
loadbalance: random
cluster: failfast
timeout: 6666
...
...

View File

@ -0,0 +1,40 @@
#
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
#
# Application scope, apply to all services.
---
configVersion: v3.0
scope: application
key: demo-provider
configs:
- match:
address:
wildcard: "10.0.0.1:20880"
service:
oneof:
- exact: "DemoService"
- exact: "DemoService2"
param:
- key: match_key1
value:
exact: value1
side: provider
parameters:
weight: 200
...

View File

@ -0,0 +1,38 @@
#
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
#
# Application scope, apply to all services.
---
configVersion: v3.0
scope: application
key: demo-provider
configs:
- addresses:
- "10.0.0.1:20880" # compatibility test
services:
- "DemoService" # compatibility test
match:
param:
- key: match_key1
value:
exact: value1
side: provider
parameters:
weight: 200
...

View File

@ -0,0 +1,43 @@
#
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
#
# Application scope, apply to all services.
---
configVersion: v3.0
scope: application
key: demo-provider
configs:
- addresses:
- "10.0.0.1:20880" # duplicate test
services: [ "DemoService" ] # duplicate test
match:
address:
wildcard: "10.0.0.1:20880"
service:
oneof:
- exact: "DemoService"
- exact: "DemoService2"
param:
- key: match_key1
value:
exact: value1
side: provider
parameters:
weight: 200
...

View File

@ -0,0 +1,35 @@
#
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#
#
# Application scope, no service means apply to all
---
configVersion: v3.0
key: demo-provider
type: javascript
script: |
function route(invokers) {
var result = new java.util.ArrayList(invokers.size());
for (i = 0; i < invokers.size(); i ++) {
if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) {
result.add(invokers.get(i));
}
}
return result;
} (invokers); // 表示立即执行方法
...

View File

@ -23,8 +23,16 @@
<param name="ConversionPattern" value="[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n"/>
</layout>
</appender>
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n"/>
</layout>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="DUBBO"/>
<appender-ref ref="CONSOLE"/>
</root>
</log4j:configuration>
</log4j:configuration>

View File

@ -65,12 +65,12 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@ -100,8 +100,38 @@
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>get-version-infos</id>
<phase>compile</phase>
<configuration>
<failOnError>true</failOnError>
<target>
<property name="version_file"
value="${project.build.outputDirectory}/META-INF/version"/>
<!-- get the current version of dubbo -->
<echo message="revision=${revision}${line.separator}" file="${version_file}"/>
<echo message="git.commit.id=" file="${version_file}" append="true"/>
<!-- get the latest commit id -->
<exec executable="git" output="${version_file}" error=".git.exec.error" append="true"
timeout="3000" failifexecutionfails="false">
<arg line="rev-parse HEAD"/>
</exec>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -20,6 +20,7 @@ import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
import org.apache.dubbo.common.config.ConfigurationCache;
import org.apache.dubbo.common.convert.ConverterUtil;
import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
import org.apache.dubbo.common.ssl.CertManager;
import org.apache.dubbo.common.status.reporter.FrameworkStatusReportService;
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
import org.apache.dubbo.common.utils.DefaultSerializeClassChecker;
@ -38,6 +39,7 @@ public class CommonScopeModelInitializer implements ScopeModelInitializer {
beanFactory.registerBean(ConverterUtil.class);
beanFactory.registerBean(SerializeSecurityManager.class);
beanFactory.registerBean(DefaultSerializeClassChecker.class);
beanFactory.registerBean(CertManager.class);
}
@Override

View File

@ -57,6 +57,7 @@ import java.util.TreeMap;
import java.util.function.Predicate;
import static org.apache.dubbo.common.BaseServiceMetadata.COLON_SEPARATOR;
import static org.apache.dubbo.common.constants.CommonConstants.ADDRESS_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
@ -115,7 +116,7 @@ class URL implements Serializable {
private static final long serialVersionUID = -1985165475234910535L;
private static Map<String, URL> cachedURLs = new LRUCache<>();
private static final Map<String, URL> cachedURLs = new LRUCache<>();
private final URLAddress urlAddress;
private final URLParam urlParam;
@ -532,6 +533,10 @@ class URL implements Serializable {
return path;
}
public Map<String, String> getOriginalParameters() {
return this.getParameters();
}
public Map<String, String> getParameters() {
return urlParam.getParameters();
}
@ -566,6 +571,10 @@ class URL implements Serializable {
return decode(getParameter(key, defaultValue));
}
public String getOriginalParameter(String key) {
return getParameter(key);
}
public String getParameter(String key) {
return urlParam.getParameter(key);
}
@ -1097,8 +1106,17 @@ class URL implements Serializable {
return urlParam.getParameter(key);
}
public Map<String, String> toOriginalMap() {
Map<String, String> map = new HashMap<>(getOriginalParameters());
return addSpecialKeys(map);
}
public Map<String, String> toMap() {
Map<String, String> map = new HashMap<>(getParameters());
return addSpecialKeys(map);
}
private Map<String, String> addSpecialKeys(Map<String, String> map) {
if (getProtocol() != null) {
map.put(PROTOCOL_KEY, getProtocol());
}
@ -1117,6 +1135,9 @@ class URL implements Serializable {
if (getPath() != null) {
map.put(PATH_KEY, getPath());
}
if (getAddress() != null) {
map.put(ADDRESS_KEY, getAddress());
}
return map;
}
@ -1596,10 +1617,18 @@ class URL implements Serializable {
}
/* add service scope operations, see InstanceAddressURL */
public Map<String, String> getOriginalServiceParameters(String service) {
return getServiceParameters(service);
}
public Map<String, String> getServiceParameters(String service) {
return getParameters();
}
public String getOriginalServiceParameter(String service, String key) {
return this.getServiceParameter(service, key);
}
public String getServiceParameter(String service, String key) {
return getParameter(key);
}

View File

@ -43,9 +43,9 @@ public final class URLBuilder extends ServiceConfigURL {
private String path;
private Map<String, String> parameters;
private final Map<String, String> parameters;
private Map<String, Object> attributes;
private final Map<String, Object> attributes;
private Map<String, Map<String, String>> methodParameters;

View File

@ -278,7 +278,7 @@ public final class URLStrParser {
String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf);
String value;
if (valueStart >= valueEnd) {
value = name;
value = "";
} else {
value = decodeComponent(str, valueStart, valueEnd, false, tempBuf);
}
@ -291,7 +291,7 @@ public final class URLStrParser {
String name = str.substring(nameStart, valueStart - 1);
String value;
if (valueStart >= valueEnd) {
value = name;
value = "";
} else {
value = str.substring(valueStart, valueEnd);
}

View File

@ -19,15 +19,19 @@ package org.apache.dubbo.common;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ClassUtils;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.StringUtils;
import java.io.IOException;
import java.net.URL;
import java.security.CodeSource;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -46,8 +50,9 @@ public final class Version {
public static final String DEFAULT_DUBBO_PROTOCOL_VERSION = "2.0.2";
// version 1.0.0 represents Dubbo rpc protocol before v2.6.2
public static final int LEGACY_DUBBO_PROTOCOL_VERSION = 10000; // 1.0.0
// Dubbo implementation version, usually is jar version.
private static final String VERSION = getVersion(Version.class, "");
// Dubbo implementation version.
private static String VERSION;
private static String LATEST_COMMIT_ID;
/**
* For protocol compatibility purpose.
@ -61,6 +66,21 @@ public final class Version {
static {
// check if there's duplicated jar
Version.checkDuplicate(Version.class);
// get dubbo version and last commit id
try {
Properties properties =
ConfigUtils.loadProperties(Collections.emptySet(), "META-INF/version");
VERSION = Optional.ofNullable(properties.getProperty("revision"))
.filter(StringUtils::isNotBlank)
.orElseGet(() -> getVersion(Version.class, ""));
LATEST_COMMIT_ID = Optional.ofNullable(properties.getProperty("git.commit.id")).orElse("");
} catch (Throwable e) {
logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", "continue the old logic, ignore exception " + e.getMessage(), e);
VERSION = getVersion(Version.class, "");
LATEST_COMMIT_ID = "";
}
}
private Version() {
@ -74,6 +94,10 @@ public final class Version {
return VERSION;
}
public static String getLastCommitId() {
return LATEST_COMMIT_ID;
}
/**
* Compare versions
*

View File

@ -47,12 +47,13 @@ public class ScopeBeanFactory {
protected static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(ScopeBeanFactory.class);
private final ScopeBeanFactory parent;
private ExtensionAccessor extensionAccessor;
private List<ExtensionPostProcessor> extensionPostProcessors;
private ConcurrentHashMap<Class, AtomicInteger> beanNameIdCounterMap = new ConcurrentHashMap<>();
private List<BeanInfo> registeredBeanInfos = new CopyOnWriteArrayList<>();
private final ExtensionAccessor extensionAccessor;
private final List<ExtensionPostProcessor> extensionPostProcessors;
private final ConcurrentHashMap<Class<?>, AtomicInteger> beanNameIdCounterMap = new ConcurrentHashMap<>();
private final List<BeanInfo> registeredBeanInfos = new CopyOnWriteArrayList<>();
private InstantiationStrategy instantiationStrategy;
private AtomicBoolean destroyed = new AtomicBoolean();
private final AtomicBoolean destroyed = new AtomicBoolean();
private List<Class<?>> registeredClasses = new ArrayList<>();
public ScopeBeanFactory(ScopeBeanFactory parent, ExtensionAccessor extensionAccessor) {
this.parent = parent;
@ -131,6 +132,7 @@ public class ScopeBeanFactory {
}
}
}
registeredClasses.add(type);
return bean;
}
@ -207,6 +209,7 @@ public class ScopeBeanFactory {
return bean;
}
@SuppressWarnings("unchecked")
private <T> T getBeanInternal(String name, Class<T> type) {
checkDestroyed();
// All classes are derived from java.lang.Object, cannot filter bean by it
@ -272,12 +275,16 @@ public class ScopeBeanFactory {
}
static class BeanInfo {
private String name;
private Object instance;
private final String name;
private final Object instance;
public BeanInfo(String name, Object instance) {
this.name = name;
this.instance = instance;
}
}
public List<Class<?>> getRegisteredClasses() {
return registeredClasses;
}
}

View File

@ -57,7 +57,7 @@ public final class JavaBeanDescriptor implements Serializable, Iterable<Map.Entr
private String className;
private int type;
private Map<Object, Object> properties = new LinkedHashMap<>();
private final Map<Object, Object> properties = new LinkedHashMap<>();
public JavaBeanDescriptor() {}

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