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;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/AbstractRouterRule.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/AbstractRouterRule.java
index ea65984e8c..3ed98cf0f4 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/AbstractRouterRule.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/AbstractRouterRule.java
@@ -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;
+ }
}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java
index be4774e429..ddbb1966e6 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionStateRouter.java
@@ -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'
+ *
+ * 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}
+ *
+ * ---
+ * 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 extends AbstractStateRouter {
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 whenCondition;
- protected Map thenCondition;
+ protected Map whenCondition;
+ protected Map thenCondition;
+ protected List 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 extends AbstractStateRouter {
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 extends AbstractStateRouter {
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 when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap() : parseRule(whenRule);
- Map then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
+ Map when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<>() : parseRule(whenRule);
+ Map 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 extends AbstractStateRouter {
}
}
- private static Map parseRule(String rule)
- throws ParseException {
- Map condition = new HashMap();
+ private Map parseRule(String rule)
+ throws ParseException {
+ Map 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 values = null;
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
@@ -125,40 +136,40 @@ public class ConditionStateRouter extends AbstractStateRouter {
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 extends AbstractStateRouter {
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 condition, URL url, URL param, Invocation invocation) {
- Map sample = url.toMap();
- boolean result = false;
- for (Map.Entry 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 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 matches = new HashSet();
- final Set mismatches = new HashSet();
-
- 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 conditions, boolean isWhenCondition) {
+ Map sample = url.toOriginalMap();
+ for (Map.Entry entry : conditions.entrySet()) {
+ ConditionMatcher matchPair = entry.getValue();
+ if (!matchPair.isMatch(sample, param, invocation, isWhenCondition)) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/AppStateRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/AppStateRouterFactory.java
index ecfb2220a1..14d05cde10 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/AppStateRouterFactory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/AppStateRouterFactory.java
@@ -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 StateRouter getRouter(Class interfaceClass, URL url) {
if (router != null) {
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
index 2348a3e23c..40758c4f3e 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
@@ -48,12 +48,12 @@ import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAIL
*/
public abstract class ListenableStateRouter extends AbstractStateRouter 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> conditionRouters = Collections.emptyList();
- private String ruleKey;
+ private final String ruleKey;
public ListenableStateRouter(URL url, String ruleKey) {
super(url);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ProviderAppStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ProviderAppStateRouter.java
new file mode 100644
index 0000000000..9057ac455a
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ProviderAppStateRouter.java
@@ -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 extends ListenableStateRouter {
+ 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> invokers) {
+ if (CollectionUtils.isEmpty(invokers)) {
+ return;
+ }
+
+ Invoker 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));
+ }
+ }
+ }
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ProviderAppStateRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ProviderAppStateRouterFactory.java
new file mode 100644
index 0000000000..5b8a560f07
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ProviderAppStateRouterFactory.java
@@ -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 StateRouter createRouter(Class interfaceClass, URL url) {
+ return new ProviderAppStateRouter<>(url);
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/AbstractConditionMatcher.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/AbstractConditionMatcher.java
new file mode 100644
index 0000000000..1dc8310700
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/AbstractConditionMatcher.java
@@ -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 matches = new HashSet<>();
+ final Set mismatches = new HashSet<>();
+ private final ModuleModel model;
+ private final List 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 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 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 getMatches() {
+ return matches;
+ }
+
+ @Override
+ public Set 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 sample, URL url, Invocation invocation);
+
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/ConditionMatcher.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/ConditionMatcher.java
new file mode 100644
index 0000000000..be3a55247a
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/ConditionMatcher.java
@@ -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.
+ *
+ * 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'
+ *
+ * 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 sample, URL param, Invocation invocation, boolean isWhenCondition);
+
+ /**
+ * match patterns extracted from when condition
+ *
+ * @return
+ */
+ Set getMatches();
+
+ /**
+ * mismatch patterns extracted from then condition
+ *
+ * @return
+ */
+ Set getMismatches();
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/ConditionMatcherFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/ConditionMatcherFactory.java
new file mode 100644
index 0000000000..9ee338a8e6
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/ConditionMatcherFactory.java
@@ -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);
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/argument/ArgumentConditionMatcher.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/argument/ArgumentConditionMatcher.java
new file mode 100644
index 0000000000..d66bff1f38
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/argument/ArgumentConditionMatcher.java
@@ -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 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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/argument/ArgumentConditionMatcherFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/argument/ArgumentConditionMatcherFactory.java
new file mode 100644
index 0000000000..c9fcf5fe58
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/argument/ArgumentConditionMatcherFactory.java
@@ -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);
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/attachment/AttachmentConditionMatcher.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/attachment/AttachmentConditionMatcher.java
new file mode 100644
index 0000000000..c7dc148100
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/attachment/AttachmentConditionMatcher.java
@@ -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 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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/attachment/AttachmentConditionMatcherFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/attachment/AttachmentConditionMatcherFactory.java
new file mode 100644
index 0000000000..186510304a
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/attachment/AttachmentConditionMatcherFactory.java
@@ -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);
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/param/UrlParamConditionMatcher.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/param/UrlParamConditionMatcher.java
new file mode 100644
index 0000000000..b1a8d5ba58
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/param/UrlParamConditionMatcher.java
@@ -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 sample, URL url, Invocation invocation) {
+ return getSampleValueFromUrl(key, sample, url, invocation);
+ }
+
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/param/UrlParamConditionMatcherFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/param/UrlParamConditionMatcherFactory.java
new file mode 100644
index 0000000000..5c216f1743
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/param/UrlParamConditionMatcherFactory.java
@@ -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);
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/ValuePattern.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/ValuePattern.java
new file mode 100644
index 0000000000..82c38a47eb
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/ValuePattern.java
@@ -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);
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/range/RangeValuePattern.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/range/RangeValuePattern.java
new file mode 100644
index 0000000000..f876b3b377
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/range/RangeValuePattern.java
@@ -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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/wildcard/WildcardValuePattern.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/wildcard/WildcardValuePattern.java
new file mode 100644
index 0000000000..d712ad77ff
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/matcher/pattern/wildcard/WildcardValuePattern.java
@@ -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=*'
+ *
+ * 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);
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleCache.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleCache.java
index 499a7ea256..097162e927 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleCache.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleCache.java
@@ -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 {
List> subsetInvokers = subsetMap.computeIfAbsent(subsetName, (k) -> new BitList<>(invokers.getOriginList(), true));
Map 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 {
return new MeshRuleCache<>(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), BitList.emptyList());
}
- protected static boolean containMapKeyValue(Map originMap, Map inputMap) {
+ protected static boolean isLabelMatch(URL url, String protocolServiceKey, Map inputMap) {
if (inputMap == null || inputMap.size() == 0) {
return true;
}
@@ -142,7 +143,7 @@ public class MeshRuleCache {
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;
}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/AddressMatch.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/AddressMatch.java
new file mode 100644
index 0000000000..b8fdc3b578
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/AddressMatch.java
@@ -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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/StringMatch.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/StringMatch.java
index cffe11ca1b..dda2de7df3 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/StringMatch.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/rule/virtualservice/match/StringMatch.java
@@ -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{" +
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/AppScriptRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/AppScriptRouterFactory.java
new file mode 100644
index 0000000000..52a2b3e693
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/AppScriptRouterFactory.java
@@ -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 StateRouter createRouter(Class interfaceClass, URL url) {
+ return new AppScriptStateRouter<>(url);
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/AppScriptStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/AppScriptStateRouter.java
new file mode 100644
index 0000000000..8b158e31fd
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/AppScriptStateRouter.java
@@ -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 extends AbstractStateRouter implements ConfigurationListener {
+ private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AppScriptStateRouter.class);
+ private static final String RULE_SUFFIX = ".script-router";
+
+ private ScriptRule scriptRule;
+ private ScriptStateRouter scriptRouter;
+ private String application;
+
+ public AppScriptStateRouter(URL url) {
+ super(url);
+ }
+
+ @Override
+ protected BitList> doRoute(BitList> invokers,
+ URL url,
+ Invocation invocation,
+ boolean needToPrintMessage,
+ Holder> routerSnapshotNodeHolder,
+ Holder 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> invokers) {
+ if (CollectionUtils.isEmpty(invokers)) {
+ return;
+ }
+
+ Invoker 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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/model/ScriptRule.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/model/ScriptRule.java
new file mode 100644
index 0000000000..5ebf7217ee
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/script/config/model/ScriptRule.java
@@ -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 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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java
index 2da97cb2e1..2b25ed5564 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/AbstractStateRouter.java
@@ -43,8 +43,10 @@ public abstract class AbstractStateRouter implements StateRouter {
*/
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"));
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/BitList.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/BitList.java
index 9694bf9f51..b3fda63948 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/BitList.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/BitList.java
@@ -52,7 +52,7 @@ import java.util.concurrent.ThreadLocalRandom;
* @param
* @since 3.0
*/
-public class BitList extends AbstractList {
+public class BitList extends AbstractList implements Cloneable {
private final BitSet rootSet;
private volatile List originList;
private final static BitList emptyList = new BitList(Collections.emptyList());
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/StateRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/StateRouterFactory.java
index 7f61f36d1f..5a7b779422 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/StateRouterFactory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/state/StateRouterFactory.java
@@ -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)
StateRouter getRouter(Class interfaceClass, URL url);
}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
index 20aee26407..5b8f9519ea 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java
@@ -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 extends AbstractStateRouter 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> invokers = BitList.emptyList();
public TagStateRouter(URL url) {
super(url);
@@ -71,7 +72,9 @@ public class TagStateRouter extends AbstractStateRouter 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 extends AbstractStateRouter implements Configu
// if we are requesting for a Provider with a specific tag
if (StringUtils.isNotEmpty(tag)) {
- List addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
+ Set 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 extends AbstractStateRouter implements Configu
}
// FAILOVER: return all Providers without any tags.
else {
- BitList> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
- tagRouterRuleCopy.getAddresses()));
+ BitList> 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 extends AbstractStateRouter implements Configu
} else {
// List addresses = tagRouterRule.filter(providerApp);
// return all addresses in dynamic tag group.
- List addresses = tagRouterRuleCopy.getAddresses();
+ Set 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 extends AbstractStateRouter 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 extends AbstractStateRouter implements Configu
return newInvokers;
}
- private boolean addressMatches(URL url, List addresses) {
+ private boolean addressMatches(URL url, Set addresses) {
return addresses != null && checkAddressMatch(addresses, url.getHost(), url.getPort());
}
- private boolean addressNotMatches(URL url, List addresses) {
+ private boolean addressNotMatches(URL url, Set addresses) {
return addresses == null || !checkAddressMatch(addresses, url.getHost(), url.getPort());
}
- private boolean checkAddressMatch(List addresses, String host, int port) {
+ private boolean checkAddressMatch(Set addresses, String host, int port) {
for (String address : addresses) {
try {
if (NetUtils.matchIpExpression(address, host, port)) {
@@ -237,7 +242,7 @@ public class TagStateRouter extends AbstractStateRouter 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 extends AbstractStateRouter implements Configu
@Override
public void notify(BitList> invokers) {
+ this.invokers = invokers;
if (CollectionUtils.isEmpty(invokers)) {
return;
}
@@ -275,14 +281,29 @@ public class TagStateRouter extends AbstractStateRouter 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> 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;
+ }
}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/ParamMatch.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/ParamMatch.java
new file mode 100644
index 0000000000..bef3366e83
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/ParamMatch.java
@@ -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;
+ }
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/Tag.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/Tag.java
index 706384c3a4..13c94d7911 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/Tag.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/model/Tag.java
@@ -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 match;
private List addresses;
@SuppressWarnings("unchecked")
- public static Tag parseFromMap(Map map) {
+ public static Tag parseFromMap(Map 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