directory) throws RpcException;
+ static Cluster getCluster(String name) {
+ return getCluster(name, true);
+ }
+
+ static Cluster getCluster(String name, boolean wrap) {
+ if (StringUtils.isEmpty(name)) {
+ name = Cluster.DEFAULT;
+ }
+ return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(name, wrap);
+ }
}
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
new file mode 100644
index 00000000000..4b9199e0e02
--- /dev/null
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/ClusterInvoker.java
@@ -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.
+ */
+package org.apache.dubbo.rpc.cluster;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Invoker;
+
+/**
+ * This is the final Invoker type referenced by the RPC proxy on Consumer side.
+ *
+ * A ClusterInvoker holds a group of normal invokers, stored in a Directory, mapping to one Registry.
+ * The ClusterInvoker implementation usually provides LB or HA policies, like FailoverClusterInvoker.
+ *
+ * In multi-registry subscription scenario, the final ClusterInvoker will referr to several sub ClusterInvokers, with each
+ * sub ClusterInvoker representing one Registry. Take ZoneAwareClusterInvoker as an example, it is specially customized for
+ * multi-registry use cases: first, pick up one ClusterInvoker, then do LB inside the chose ClusterInvoker.
+ *
+ * @param
+ */
+public interface ClusterInvoker extends Invoker {
+ URL getRegistryUrl();
+
+ Directory getDirectory();
+}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
index 814fb64c7a5..7984d7d2bee 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/directory/AbstractDirectory.java
@@ -60,8 +60,7 @@ public AbstractDirectory(URL url, RouterChain routerChain) {
}
this.url = url.removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
- this.consumerUrl = url.addParameters(StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY)))
- .removeParameter(MONITOR_KEY);
+ this.consumerUrl = this.url.addParameters(StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY)));
setRouterChain(routerChain);
}
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java
index 74dfce22c33..a3514c1dcc9 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java
@@ -92,7 +92,7 @@ public List> route(List> invokers, URL url, Invocation
@Override
public int getPriority() {
- return DEFAULT_PRIORITY;
+ return priority;
}
@Override
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java
index cfe1a70a160..49893b7aae4 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mock/MockInvokersSelector.java
@@ -36,7 +36,7 @@
public class MockInvokersSelector extends AbstractRouter {
public static final String NAME = "MOCK_ROUTER";
- private static final int MOCK_INVOKERS_DEFAULT_PRIORITY = Integer.MIN_VALUE;
+ private static final int MOCK_INVOKERS_DEFAULT_PRIORITY = -100;
public MockInvokersSelector() {
this.priority = MOCK_INVOKERS_DEFAULT_PRIORITY;
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
index a4b9e92cd17..b1e8a95e89a 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
@@ -30,6 +30,7 @@
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.support.RpcUtils;
@@ -49,7 +50,7 @@
/**
* AbstractClusterInvoker
*/
-public abstract class AbstractClusterInvoker implements Invoker {
+public abstract class AbstractClusterInvoker implements ClusterInvoker {
private static final Logger logger = LoggerFactory.getLogger(AbstractClusterInvoker.class);
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
index f9f2ae72eaf..5585b35c94a 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/registry/ZoneAwareClusterInvoker.java
@@ -23,6 +23,7 @@
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
@@ -59,24 +60,23 @@ public ZoneAwareClusterInvoker(Directory directory) {
public Result doInvoke(Invocation invocation, final List> invokers, LoadBalance loadbalance) throws RpcException {
// First, pick the invoker (XXXClusterInvoker) that comes from the local registry, distinguish by a 'preferred' key.
for (Invoker invoker : invokers) {
- // FIXME, the invoker is a cluster invoker representing one Registry, so it will automatically wrapped by MockClusterInvoker.
- MockClusterInvoker mockClusterInvoker = (MockClusterInvoker) invoker;
- if (mockClusterInvoker.isAvailable() && mockClusterInvoker.getRegistryUrl()
+ ClusterInvoker clusterInvoker = (ClusterInvoker) invoker;
+ if (clusterInvoker.isAvailable() && clusterInvoker.getRegistryUrl()
.getParameter(REGISTRY_KEY + "." + PREFERRED_KEY, false)) {
- return mockClusterInvoker.invoke(invocation);
+ return clusterInvoker.invoke(invocation);
}
}
// providers in the registry with the same zone
- String zone = (String) invocation.getAttachment(REGISTRY_ZONE);
+ String zone = invocation.getAttachment(REGISTRY_ZONE);
if (StringUtils.isNotEmpty(zone)) {
for (Invoker invoker : invokers) {
- MockClusterInvoker mockClusterInvoker = (MockClusterInvoker) invoker;
- if (mockClusterInvoker.isAvailable() && zone.equals(mockClusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY + "." + ZONE_KEY))) {
- return mockClusterInvoker.invoke(invocation);
+ ClusterInvoker clusterInvoker = (ClusterInvoker) invoker;
+ if (clusterInvoker.isAvailable() && zone.equals(clusterInvoker.getRegistryUrl().getParameter(REGISTRY_KEY + "." + ZONE_KEY))) {
+ return clusterInvoker.invoke(invocation);
}
}
- String force = (String) invocation.getAttachment(REGISTRY_ZONE_FORCE);
+ String force = invocation.getAttachment(REGISTRY_ZONE_FORCE);
if (StringUtils.isNotEmpty(force) && "true".equalsIgnoreCase(force)) {
throw new IllegalStateException("No registry instance in zone or no available providers in the registry, zone: "
+ zone
@@ -93,12 +93,14 @@ public Result doInvoke(Invocation invocation, final List> invokers, L
// If none of the invokers has a preferred signal or is picked by the loadbalancer, pick the first one available.
for (Invoker invoker : invokers) {
- MockClusterInvoker mockClusterInvoker = (MockClusterInvoker) invoker;
- if (mockClusterInvoker.isAvailable()) {
- return mockClusterInvoker.invoke(invocation);
+ ClusterInvoker clusterInvoker = (ClusterInvoker) invoker;
+ if (clusterInvoker.isAvailable()) {
+ return clusterInvoker.invoke(invocation);
}
}
- throw new RpcException("No provider available in " + invokers);
+
+ //if none available,just pick one
+ return invokers.get(0).invoke(invocation);
}
-}
+}
\ No newline at end of file
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
index afdeddee9e5..90c43bd7bcf 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvoker.java
@@ -27,6 +27,7 @@
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.support.MockInvoker;
@@ -35,7 +36,7 @@
import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK;
-public class MockClusterInvoker implements Invoker {
+public class MockClusterInvoker implements ClusterInvoker {
private static final Logger logger = LoggerFactory.getLogger(MockClusterInvoker.class);
@@ -57,6 +58,11 @@ public URL getRegistryUrl() {
return directory.getUrl();
}
+ @Override
+ public Directory getDirectory() {
+ return directory;
+ }
+
@Override
public boolean isAvailable() {
return directory.isAvailable();
diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
index a27a70e4f56..a6ec35b1ef5 100644
--- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
+++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvokerTest.java
@@ -227,7 +227,7 @@ public void testSelect_multiInvokers() throws Exception {
public void testCloseAvailablecheck() {
LoadBalance lb = mock(LoadBalance.class);
Map queryMap = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
- URL tmpUrl = url.addParameters(queryMap).removeParameter(MONITOR_KEY);
+ URL tmpUrl = url.addParameters(queryMap).removeParameter(REFER_KEY).removeParameter(MONITOR_KEY);
given(lb.select(invokers, tmpUrl, invocation)).willReturn(invoker1);
initlistsize5();
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
index bf1fa22df80..31c3684fa3a 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfiguration.java
@@ -236,7 +236,7 @@ protected ThreadPoolExecutor initWorkersThreadPool(String threadPoolPrefixName,
int threadPoolSize,
long keepAliveTime) {
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, keepAliveTime,
- TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory(threadPoolPrefixName));
+ TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory(threadPoolPrefixName, true));
}
protected static String getThreadPoolPrefixName(URL url) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
index 5a1aefda0c0..0c9178f359d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfiguration.java
@@ -24,6 +24,7 @@
import org.apache.dubbo.common.config.configcenter.TreePathDynamicConfiguration;
import org.apache.dubbo.common.function.ThrowableConsumer;
import org.apache.dubbo.common.function.ThrowableFunction;
+import org.apache.dubbo.common.lang.ShutdownHookCallbacks;
import org.apache.dubbo.common.utils.NamedThreadFactory;
import org.apache.dubbo.common.utils.StringUtils;
@@ -146,6 +147,7 @@ public class FileSystemDynamicConfiguration extends TreePathDynamicConfiguration
MODIFIERS = initWatchEventModifiers();
DELAY = initDelay(MODIFIERS);
WATCH_EVENTS_LOOP_THREAD_POOL = newWatchEventsLoopThreadPool();
+ registerDubboShutdownHook();
}
/**
@@ -230,6 +232,24 @@ private void doInListener(String configFilePath, BiConsumer {
+ watchService.ifPresent(w -> {
+ try {
+ w.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ getWatchEventsLoopThreadPool().shutdown();
+ });
+ }
+
private static boolean isProcessingWatchEvents() {
return getWatchEventsLoopThreadPool().getActiveCount() > 0;
}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
index cf34202dcfc..922b04ac54b 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java
@@ -1,1024 +1,1045 @@
-/*
- * 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.common.extension;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.context.Lifecycle;
-import org.apache.dubbo.common.extension.support.ActivateComparator;
-import org.apache.dubbo.common.lang.Prioritized;
-import org.apache.dubbo.common.logger.Logger;
-import org.apache.dubbo.common.logger.LoggerFactory;
-import org.apache.dubbo.common.utils.ArrayUtils;
-import org.apache.dubbo.common.utils.ClassUtils;
-import org.apache.dubbo.common.utils.CollectionUtils;
-import org.apache.dubbo.common.utils.ConcurrentHashSet;
-import org.apache.dubbo.common.utils.ConfigUtils;
-import org.apache.dubbo.common.utils.Holder;
-import org.apache.dubbo.common.utils.ReflectUtils;
-import org.apache.dubbo.common.utils.StringUtils;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.ServiceLoader;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.regex.Pattern;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.sort;
-import static java.util.ServiceLoader.load;
-import static java.util.stream.StreamSupport.stream;
-import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
-import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
-import static org.apache.dubbo.common.constants.CommonConstants.REMOVE_VALUE_PREFIX;
-
-/**
- * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
- * at present designed to be singleton or static (by itself totally static or uses some static fields).
- * So the instances returned from them are of process or classloader scope. If you want to support
- * multiple dubbo servers in a single process, you may need to refactor these three classes.
- *
- * Load dubbo extensions
- *
- * auto inject dependency extension
- * auto wrap extension in wrapper
- * default extension is an adaptive instance
- *
- *
- * @see Service Provider in Java 5
- * @see org.apache.dubbo.common.extension.SPI
- * @see org.apache.dubbo.common.extension.Adaptive
- * @see org.apache.dubbo.common.extension.Activate
- */
-public class ExtensionLoader {
-
- private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
-
- private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
-
- private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
-
- private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
-
- private final Class> type;
-
- private final ExtensionFactory objectFactory;
-
- private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>();
-
- private final Holder>> cachedClasses = new Holder<>();
-
- private final Map cachedActivates = new ConcurrentHashMap<>();
- private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>();
- private final Holder cachedAdaptiveInstance = new Holder<>();
- private volatile Class> cachedAdaptiveClass = null;
- private String cachedDefaultName;
- private volatile Throwable createAdaptiveInstanceError;
-
- private Set> cachedWrapperClasses;
-
- private Map exceptions = new ConcurrentHashMap<>();
-
- private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
-
- public static void setLoadingStrategies(LoadingStrategy... strategies) {
- if (ArrayUtils.isNotEmpty(strategies)) {
- ExtensionLoader.strategies = strategies;
- }
- }
-
- /**
- * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
- *
- * @return non-null
- * @since 2.7.7
- */
- private static LoadingStrategy[] loadLoadingStrategies() {
- return stream(load(LoadingStrategy.class).spliterator(), false)
- .sorted()
- .toArray(LoadingStrategy[]::new);
- }
-
- /**
- * Get all {@link LoadingStrategy Loading Strategies}
- *
- * @return non-null
- * @see LoadingStrategy
- * @see Prioritized
- * @since 2.7.7
- */
- public static List getLoadingStrategies() {
- return asList(strategies);
- }
-
- private ExtensionLoader(Class> type) {
- this.type = type;
- objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
- }
-
- private static boolean withExtensionAnnotation(Class type) {
- return type.isAnnotationPresent(SPI.class);
- }
-
- @SuppressWarnings("unchecked")
- public static ExtensionLoader getExtensionLoader(Class type) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type == null");
- }
- if (!type.isInterface()) {
- throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
- }
- if (!withExtensionAnnotation(type)) {
- throw new IllegalArgumentException("Extension type (" + type +
- ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
- }
-
- ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
- if (loader == null) {
- EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
- loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
- }
- return loader;
- }
-
- // For testing purposes only
- public static void resetExtensionLoader(Class type) {
- ExtensionLoader loader = EXTENSION_LOADERS.get(type);
- if (loader != null) {
- // Remove all instances associated with this loader as well
- Map> classes = loader.getExtensionClasses();
- for (Map.Entry> entry : classes.entrySet()) {
- EXTENSION_INSTANCES.remove(entry.getValue());
- }
- classes.clear();
- EXTENSION_LOADERS.remove(type);
- }
- }
-
- public static void destroyAll() {
- EXTENSION_INSTANCES.forEach((_type, instance) -> {
- if (instance instanceof Lifecycle) {
- Lifecycle lifecycle = (Lifecycle) instance;
- try {
- lifecycle.destroy();
- } catch (Exception e) {
- logger.error("Error destroying extension " + lifecycle, e);
- }
- }
- });
- }
-
- private static ClassLoader findClassLoader() {
- return ClassUtils.getClassLoader(ExtensionLoader.class);
- }
-
- public String getExtensionName(T extensionInstance) {
- return getExtensionName(extensionInstance.getClass());
- }
-
- public String getExtensionName(Class> extensionClass) {
- getExtensionClasses();// load class
- return cachedNames.get(extensionClass);
- }
-
- /**
- * This is equivalent to {@code getActivateExtension(url, key, null)}
- *
- * @param url url
- * @param key url parameter key which used to get extension point names
- * @return extension list which are activated.
- * @see #getActivateExtension(org.apache.dubbo.common.URL, String, String)
- */
- public List getActivateExtension(URL url, String key) {
- return getActivateExtension(url, key, null);
- }
-
- /**
- * This is equivalent to {@code getActivateExtension(url, values, null)}
- *
- * @param url url
- * @param values extension point names
- * @return extension list which are activated
- * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
- */
- public List getActivateExtension(URL url, String[] values) {
- return getActivateExtension(url, values, null);
- }
-
- /**
- * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)}
- *
- * @param url url
- * @param key url parameter key which used to get extension point names
- * @param group group
- * @return extension list which are activated.
- * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
- */
- public List getActivateExtension(URL url, String key, String group) {
- String value = url.getParameter(key);
- return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
- }
-
- /**
- * Get activate extensions.
- *
- * @param url url
- * @param values extension point names
- * @param group group
- * @return extension list which are activated
- * @see org.apache.dubbo.common.extension.Activate
- */
- public List getActivateExtension(URL url, String[] values, String group) {
- List activateExtensions = new ArrayList<>();
- List names = values == null ? new ArrayList<>(0) : asList(values);
- if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
- getExtensionClasses();
- for (Map.Entry entry : cachedActivates.entrySet()) {
- String name = entry.getKey();
- Object activate = entry.getValue();
-
- String[] activateGroup, activateValue;
-
- if (activate instanceof Activate) {
- activateGroup = ((Activate) activate).group();
- activateValue = ((Activate) activate).value();
- } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
- activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
- activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
- } else {
- continue;
- }
- if (isMatchGroup(group, activateGroup)
- && !names.contains(name)
- && !names.contains(REMOVE_VALUE_PREFIX + name)
- && isActive(activateValue, url)) {
- activateExtensions.add(getExtension(name));
- }
- }
- activateExtensions.sort(ActivateComparator.COMPARATOR);
- }
- List loadedExtensions = new ArrayList<>();
- for (int i = 0; i < names.size(); i++) {
- String name = names.get(i);
- if (!name.startsWith(REMOVE_VALUE_PREFIX)
- && !names.contains(REMOVE_VALUE_PREFIX + name)) {
- if (DEFAULT_KEY.equals(name)) {
- if (!loadedExtensions.isEmpty()) {
- activateExtensions.addAll(0, loadedExtensions);
- loadedExtensions.clear();
- }
- } else {
- loadedExtensions.add(getExtension(name));
- }
- }
- }
- if (!loadedExtensions.isEmpty()) {
- activateExtensions.addAll(loadedExtensions);
- }
- return activateExtensions;
- }
-
- private boolean isMatchGroup(String group, String[] groups) {
- if (StringUtils.isEmpty(group)) {
- return true;
- }
- if (groups != null && groups.length > 0) {
- for (String g : groups) {
- if (group.equals(g)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean isActive(String[] keys, URL url) {
- if (keys.length == 0) {
- return true;
- }
- for (String key : keys) {
- // @Active(value="key1:value1, key2:value2")
- String keyValue = null;
- if (key.contains(":")) {
- String[] arr = key.split(":");
- key = arr[0];
- keyValue = arr[1];
- }
-
- for (Map.Entry entry : url.getParameters().entrySet()) {
- String k = entry.getKey();
- String v = entry.getValue();
- if ((k.equals(key) || k.endsWith("." + key))
- && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Get extension's instance. Return null
if extension is not found or is not initialized. Pls. note
- * that this method will not trigger extension load.
- *
- * In order to trigger extension load, call {@link #getExtension(String)} instead.
- *
- * @see #getExtension(String)
- */
- @SuppressWarnings("unchecked")
- public T getLoadedExtension(String name) {
- if (StringUtils.isEmpty(name)) {
- throw new IllegalArgumentException("Extension name == null");
- }
- Holder holder = getOrCreateHolder(name);
- return (T) holder.get();
- }
-
- private Holder getOrCreateHolder(String name) {
- Holder holder = cachedInstances.get(name);
- if (holder == null) {
- cachedInstances.putIfAbsent(name, new Holder<>());
- holder = cachedInstances.get(name);
- }
- return holder;
- }
-
- /**
- * Return the list of extensions which are already loaded.
- *
- * Usually {@link #getSupportedExtensions()} should be called in order to get all extensions.
- *
- * @see #getSupportedExtensions()
- */
- public Set getLoadedExtensions() {
- return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
- }
-
- public List getLoadedExtensionInstances() {
- List instances = new ArrayList<>();
- cachedInstances.values().forEach(holder -> instances.add((T) holder.get()));
- return instances;
- }
-
- public Object getLoadedAdaptiveExtensionInstances() {
- return cachedAdaptiveInstance.get();
- }
-
-// public T getPrioritizedExtensionInstance() {
-// Set supported = getSupportedExtensions();
-//
-// Set instances = new HashSet<>();
-// Set prioritized = new HashSet<>();
-// for (String s : supported) {
-//
-// }
-//
-// }
-
- /**
- * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
- * will be thrown.
- */
- @SuppressWarnings("unchecked")
- public T getExtension(String name) {
- if (StringUtils.isEmpty(name)) {
- throw new IllegalArgumentException("Extension name == null");
- }
- if ("true".equals(name)) {
- return getDefaultExtension();
- }
- final Holder holder = getOrCreateHolder(name);
- Object instance = holder.get();
- if (instance == null) {
- synchronized (holder) {
- instance = holder.get();
- if (instance == null) {
- instance = createExtension(name);
- holder.set(instance);
- }
- }
- }
- return (T) instance;
- }
-
- /**
- * Get the extension by specified name if found, or {@link #getDefaultExtension() returns the default one}
- *
- * @param name the name of extension
- * @return non-null
- */
- public T getOrDefaultExtension(String name) {
- return containsExtension(name) ? getExtension(name) : getDefaultExtension();
- }
-
- /**
- * Return default extension, return null
if it's not configured.
- */
- public T getDefaultExtension() {
- getExtensionClasses();
- if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
- return null;
- }
- return getExtension(cachedDefaultName);
- }
-
- public boolean hasExtension(String name) {
- if (StringUtils.isEmpty(name)) {
- throw new IllegalArgumentException("Extension name == null");
- }
- Class> c = this.getExtensionClass(name);
- return c != null;
- }
-
- public Set getSupportedExtensions() {
- Map> clazzes = getExtensionClasses();
- return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
- }
-
- public Set getSupportedExtensionInstances() {
- List instances = new LinkedList<>();
- Set supportedExtensions = getSupportedExtensions();
- if (CollectionUtils.isNotEmpty(supportedExtensions)) {
- for (String name : supportedExtensions) {
- instances.add(getExtension(name));
- }
- }
- // sort the Prioritized instances
- sort(instances, Prioritized.COMPARATOR);
- return new LinkedHashSet<>(instances);
- }
-
- /**
- * Return default extension name, return null
if not configured.
- */
- public String getDefaultExtensionName() {
- getExtensionClasses();
- return cachedDefaultName;
- }
-
- /**
- * Register new extension via API
- *
- * @param name extension name
- * @param clazz extension class
- * @throws IllegalStateException when extension with the same name has already been registered.
- */
- public void addExtension(String name, Class> clazz) {
- getExtensionClasses(); // load classes
-
- if (!type.isAssignableFrom(clazz)) {
- throw new IllegalStateException("Input type " +
- clazz + " doesn't implement the Extension " + type);
- }
- if (clazz.isInterface()) {
- throw new IllegalStateException("Input type " +
- clazz + " can't be interface!");
- }
-
- if (!clazz.isAnnotationPresent(Adaptive.class)) {
- if (StringUtils.isBlank(name)) {
- throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
- }
- if (cachedClasses.get().containsKey(name)) {
- throw new IllegalStateException("Extension name " +
- name + " already exists (Extension " + type + ")!");
- }
-
- cachedNames.put(clazz, name);
- cachedClasses.get().put(name, clazz);
- } else {
- if (cachedAdaptiveClass != null) {
- throw new IllegalStateException("Adaptive Extension already exists (Extension " + type + ")!");
- }
-
- cachedAdaptiveClass = clazz;
- }
- }
-
- /**
- * Replace the existing extension via API
- *
- * @param name extension name
- * @param clazz extension class
- * @throws IllegalStateException when extension to be placed doesn't exist
- * @deprecated not recommended any longer, and use only when test
- */
- @Deprecated
- public void replaceExtension(String name, Class> clazz) {
- getExtensionClasses(); // load classes
-
- if (!type.isAssignableFrom(clazz)) {
- throw new IllegalStateException("Input type " +
- clazz + " doesn't implement Extension " + type);
- }
- if (clazz.isInterface()) {
- throw new IllegalStateException("Input type " +
- clazz + " can't be interface!");
- }
-
- if (!clazz.isAnnotationPresent(Adaptive.class)) {
- if (StringUtils.isBlank(name)) {
- throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
- }
- if (!cachedClasses.get().containsKey(name)) {
- throw new IllegalStateException("Extension name " +
- name + " doesn't exist (Extension " + type + ")!");
- }
-
- cachedNames.put(clazz, name);
- cachedClasses.get().put(name, clazz);
- cachedInstances.remove(name);
- } else {
- if (cachedAdaptiveClass == null) {
- throw new IllegalStateException("Adaptive Extension doesn't exist (Extension " + type + ")!");
- }
-
- cachedAdaptiveClass = clazz;
- cachedAdaptiveInstance.set(null);
- }
- }
-
- @SuppressWarnings("unchecked")
- public T getAdaptiveExtension() {
- Object instance = cachedAdaptiveInstance.get();
- if (instance == null) {
- if (createAdaptiveInstanceError != null) {
- throw new IllegalStateException("Failed to create adaptive instance: " +
- createAdaptiveInstanceError.toString(),
- createAdaptiveInstanceError);
- }
-
- synchronized (cachedAdaptiveInstance) {
- instance = cachedAdaptiveInstance.get();
- if (instance == null) {
- try {
- instance = createAdaptiveExtension();
- cachedAdaptiveInstance.set(instance);
- } catch (Throwable t) {
- createAdaptiveInstanceError = t;
- throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
- }
- }
- }
- }
-
- return (T) instance;
- }
-
- private IllegalStateException findException(String name) {
- for (Map.Entry entry : exceptions.entrySet()) {
- if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
- return entry.getValue();
- }
- }
- StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
-
-
- int i = 1;
- for (Map.Entry entry : exceptions.entrySet()) {
- if (i == 1) {
- buf.append(", possible causes: ");
- }
-
- buf.append("\r\n(");
- buf.append(i++);
- buf.append(") ");
- buf.append(entry.getKey());
- buf.append(":\r\n");
- buf.append(StringUtils.toString(entry.getValue()));
- }
- return new IllegalStateException(buf.toString());
- }
-
- @SuppressWarnings("unchecked")
- private T createExtension(String name) {
- Class> clazz = getExtensionClasses().get(name);
- if (clazz == null) {
- throw findException(name);
- }
- try {
- T instance = (T) EXTENSION_INSTANCES.get(clazz);
- if (instance == null) {
- EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
- instance = (T) EXTENSION_INSTANCES.get(clazz);
- }
- injectExtension(instance);
- Set> wrapperClasses = cachedWrapperClasses;
- if (CollectionUtils.isNotEmpty(wrapperClasses)) {
- for (Class> wrapperClass : wrapperClasses) {
- instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
- }
- }
- initExtension(instance);
- return instance;
- } catch (Throwable t) {
- throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
- type + ") couldn't be instantiated: " + t.getMessage(), t);
- }
- }
-
- private boolean containsExtension(String name) {
- return getExtensionClasses().containsKey(name);
- }
-
- private T injectExtension(T instance) {
-
- if (objectFactory == null) {
- return instance;
- }
-
- try {
- for (Method method : instance.getClass().getMethods()) {
- if (!isSetter(method)) {
- continue;
- }
- /**
- * Check {@link DisableInject} to see if we need auto injection for this property
- */
- if (method.getAnnotation(DisableInject.class) != null) {
- continue;
- }
- Class> pt = method.getParameterTypes()[0];
- if (ReflectUtils.isPrimitives(pt)) {
- continue;
- }
-
- try {
- String property = getSetterProperty(method);
- Object object = objectFactory.getExtension(pt, property);
- if (object != null) {
- method.invoke(instance, object);
- }
- } catch (Exception e) {
- logger.error("Failed to inject via method " + method.getName()
- + " of interface " + type.getName() + ": " + e.getMessage(), e);
- }
-
- }
- } catch (Exception e) {
- logger.error(e.getMessage(), e);
- }
- return instance;
- }
-
- private void initExtension(T instance) {
- if (instance instanceof Lifecycle) {
- Lifecycle lifecycle = (Lifecycle) instance;
- lifecycle.initialize();
- }
- }
-
- /**
- * get properties name for setter, for instance: setVersion, return "version"
- *
- * return "", if setter name with length less than 3
- */
- private String getSetterProperty(Method method) {
- return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
- }
-
- /**
- * return true if and only if:
- *
- * 1, public
- *
- * 2, name starts with "set"
- *
- * 3, only has one parameter
- */
- private boolean isSetter(Method method) {
- return method.getName().startsWith("set")
- && method.getParameterTypes().length == 1
- && Modifier.isPublic(method.getModifiers());
- }
-
- private Class> getExtensionClass(String name) {
- if (type == null) {
- throw new IllegalArgumentException("Extension type == null");
- }
- if (name == null) {
- throw new IllegalArgumentException("Extension name == null");
- }
- return getExtensionClasses().get(name);
- }
-
- private Map> getExtensionClasses() {
- Map> classes = cachedClasses.get();
- if (classes == null) {
- synchronized (cachedClasses) {
- classes = cachedClasses.get();
- if (classes == null) {
- classes = loadExtensionClasses();
- cachedClasses.set(classes);
- }
- }
- }
- return classes;
- }
-
- /**
- * synchronized in getExtensionClasses
- */
- private Map> loadExtensionClasses() {
- cacheDefaultExtensionName();
-
- Map> extensionClasses = new HashMap<>();
-
- for (LoadingStrategy strategy : strategies) {
- loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
- loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
- }
-
- return extensionClasses;
- }
-
- /**
- * extract and cache default extension name if exists
- */
- private void cacheDefaultExtensionName() {
- final SPI defaultAnnotation = type.getAnnotation(SPI.class);
- if (defaultAnnotation == null) {
- return;
- }
-
- String value = defaultAnnotation.value();
- if ((value = value.trim()).length() > 0) {
- String[] names = NAME_SEPARATOR.split(value);
- if (names.length > 1) {
- throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
- + ": " + Arrays.toString(names));
- }
- if (names.length == 1) {
- cachedDefaultName = names[0];
- }
- }
- }
-
- private void loadDirectory(Map> extensionClasses, String dir, String type) {
- loadDirectory(extensionClasses, dir, type, false, false);
- }
-
- private void loadDirectory(Map> extensionClasses, String dir, String type,
- boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
- String fileName = dir + type;
- try {
- Enumeration urls = null;
- ClassLoader classLoader = findClassLoader();
-
- // try to load from ExtensionLoader's ClassLoader first
- if (extensionLoaderClassLoaderFirst) {
- ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
- if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
- urls = extensionLoaderClassLoader.getResources(fileName);
- }
- }
-
- if (urls == null || !urls.hasMoreElements()) {
- if (classLoader != null) {
- urls = classLoader.getResources(fileName);
- } else {
- urls = ClassLoader.getSystemResources(fileName);
- }
- }
-
- if (urls != null) {
- while (urls.hasMoreElements()) {
- java.net.URL resourceURL = urls.nextElement();
- loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
- }
- }
- } catch (Throwable t) {
- logger.error("Exception occurred when loading extension class (interface: " +
- type + ", description file: " + fileName + ").", t);
- }
- }
-
- private void loadResource(Map> extensionClasses, ClassLoader classLoader,
- java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
- try {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- final int ci = line.indexOf('#');
- if (ci >= 0) {
- line = line.substring(0, ci);
- }
- line = line.trim();
- if (line.length() > 0) {
- try {
- String name = null;
- int i = line.indexOf('=');
- if (i > 0) {
- name = line.substring(0, i).trim();
- line = line.substring(i + 1).trim();
- }
- if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
- loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
- }
- } catch (Throwable t) {
- IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
- exceptions.put(line, e);
- }
- }
- }
- }
- } catch (Throwable t) {
- logger.error("Exception occurred when loading extension class (interface: " +
- type + ", class file: " + resourceURL + ") in " + resourceURL, t);
- }
- }
-
- private boolean isExcluded(String className, String... excludedPackages) {
- if (excludedPackages != null) {
- for (String excludePackage : excludedPackages) {
- if (className.startsWith(excludePackage + ".")) {
- return true;
- }
- }
- }
- return false;
- }
-
- private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name,
- boolean overridden) throws NoSuchMethodException {
- if (!type.isAssignableFrom(clazz)) {
- throw new IllegalStateException("Error occurred when loading extension class (interface: " +
- type + ", class line: " + clazz.getName() + "), class "
- + clazz.getName() + " is not subtype of interface.");
- }
- if (clazz.isAnnotationPresent(Adaptive.class)) {
- cacheAdaptiveClass(clazz, overridden);
- } else if (isWrapperClass(clazz)) {
- cacheWrapperClass(clazz);
- } else {
- clazz.getConstructor();
- if (StringUtils.isEmpty(name)) {
- name = findAnnotationName(clazz);
- if (name.length() == 0) {
- throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
- }
- }
-
- String[] names = NAME_SEPARATOR.split(name);
- if (ArrayUtils.isNotEmpty(names)) {
- cacheActivateClass(clazz, names[0]);
- for (String n : names) {
- cacheName(clazz, n);
- saveInExtensionClass(extensionClasses, clazz, n, overridden);
- }
- }
- }
- }
-
- /**
- * cache name
- */
- private void cacheName(Class> clazz, String name) {
- if (!cachedNames.containsKey(clazz)) {
- cachedNames.put(clazz, name);
- }
- }
-
- /**
- * put clazz in extensionClasses
- */
- private void saveInExtensionClass(Map> extensionClasses, Class> clazz, String name, boolean overridden) {
- Class> c = extensionClasses.get(name);
- if (c == null || overridden) {
- extensionClasses.put(name, clazz);
- } else if (c != clazz) {
- String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
- logger.error(duplicateMsg);
- throw new IllegalStateException(duplicateMsg);
- }
- }
-
- /**
- * cache Activate class which is annotated with Activate
- *
- * for compatibility, also cache class with old alibaba Activate annotation
- */
- private void cacheActivateClass(Class> clazz, String name) {
- Activate activate = clazz.getAnnotation(Activate.class);
- if (activate != null) {
- cachedActivates.put(name, activate);
- } else {
- // support com.alibaba.dubbo.common.extension.Activate
- com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
- if (oldActivate != null) {
- cachedActivates.put(name, oldActivate);
- }
- }
- }
-
- /**
- * cache Adaptive class which is annotated with Adaptive
- */
- private void cacheAdaptiveClass(Class> clazz, boolean overridden) {
- if (cachedAdaptiveClass == null || overridden) {
- cachedAdaptiveClass = clazz;
- } else if (!cachedAdaptiveClass.equals(clazz)) {
- throw new IllegalStateException("More than 1 adaptive class found: "
- + cachedAdaptiveClass.getName()
- + ", " + clazz.getName());
- }
- }
-
- /**
- * cache wrapper class
- *
- * like: ProtocolFilterWrapper, ProtocolListenerWrapper
- */
- private void cacheWrapperClass(Class> clazz) {
- if (cachedWrapperClasses == null) {
- cachedWrapperClasses = new ConcurrentHashSet<>();
- }
- cachedWrapperClasses.add(clazz);
- }
-
- /**
- * test if clazz is a wrapper class
- *
- * which has Constructor with given class type as its only argument
- */
- private boolean isWrapperClass(Class> clazz) {
- try {
- clazz.getConstructor(type);
- return true;
- } catch (NoSuchMethodException e) {
- return false;
- }
- }
-
- @SuppressWarnings("deprecation")
- private String findAnnotationName(Class> clazz) {
- org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
- if (extension != null) {
- return extension.value();
- }
-
- String name = clazz.getSimpleName();
- if (name.endsWith(type.getSimpleName())) {
- name = name.substring(0, name.length() - type.getSimpleName().length());
- }
- return name.toLowerCase();
- }
-
- @SuppressWarnings("unchecked")
- private T createAdaptiveExtension() {
- try {
- return injectExtension((T) getAdaptiveExtensionClass().newInstance());
- } catch (Exception e) {
- throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
- }
- }
-
- private Class> getAdaptiveExtensionClass() {
- getExtensionClasses();
- if (cachedAdaptiveClass != null) {
- return cachedAdaptiveClass;
- }
- return cachedAdaptiveClass = createAdaptiveExtensionClass();
- }
-
- private Class> createAdaptiveExtensionClass() {
- String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
- ClassLoader classLoader = findClassLoader();
- org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
- return compiler.compile(code, classLoader);
- }
-
- @Override
- public String toString() {
- return this.getClass().getName() + "[" + type.getName() + "]";
- }
-
-}
+/*
+ * 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.common.extension;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.context.Lifecycle;
+import org.apache.dubbo.common.extension.support.ActivateComparator;
+import org.apache.dubbo.common.extension.support.WrapperComparator;
+import org.apache.dubbo.common.lang.Prioritized;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.ArrayUtils;
+import org.apache.dubbo.common.utils.ClassUtils;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.ConcurrentHashSet;
+import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.Holder;
+import org.apache.dubbo.common.utils.ReflectUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Pattern;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.sort;
+import static java.util.ServiceLoader.load;
+import static java.util.stream.StreamSupport.stream;
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOVE_VALUE_PREFIX;
+
+/**
+ * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are
+ * at present designed to be singleton or static (by itself totally static or uses some static fields).
+ * So the instances returned from them are of process or classloader scope. If you want to support
+ * multiple dubbo servers in a single process, you may need to refactor these three classes.
+ *
+ * Load dubbo extensions
+ *
+ * auto inject dependency extension
+ * auto wrap extension in wrapper
+ * default extension is an adaptive instance
+ *
+ *
+ * @see Service Provider in Java 5
+ * @see org.apache.dubbo.common.extension.SPI
+ * @see org.apache.dubbo.common.extension.Adaptive
+ * @see org.apache.dubbo.common.extension.Activate
+ */
+public class ExtensionLoader {
+
+ private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
+
+ private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
+
+ private static final ConcurrentMap, ExtensionLoader>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
+
+ private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
+
+ private final Class> type;
+
+ private final ExtensionFactory objectFactory;
+
+ private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>();
+
+ private final Holder>> cachedClasses = new Holder<>();
+
+ private final Map cachedActivates = new ConcurrentHashMap<>();
+ private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>();
+ private final Holder cachedAdaptiveInstance = new Holder<>();
+ private volatile Class> cachedAdaptiveClass = null;
+ private String cachedDefaultName;
+ private volatile Throwable createAdaptiveInstanceError;
+
+ private Set> cachedWrapperClasses;
+
+ private Map exceptions = new ConcurrentHashMap<>();
+
+ private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
+
+ public static void setLoadingStrategies(LoadingStrategy... strategies) {
+ if (ArrayUtils.isNotEmpty(strategies)) {
+ ExtensionLoader.strategies = strategies;
+ }
+ }
+
+ /**
+ * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader}
+ *
+ * @return non-null
+ * @since 2.7.7
+ */
+ private static LoadingStrategy[] loadLoadingStrategies() {
+ return stream(load(LoadingStrategy.class).spliterator(), false)
+ .sorted()
+ .toArray(LoadingStrategy[]::new);
+ }
+
+ /**
+ * Get all {@link LoadingStrategy Loading Strategies}
+ *
+ * @return non-null
+ * @see LoadingStrategy
+ * @see Prioritized
+ * @since 2.7.7
+ */
+ public static List getLoadingStrategies() {
+ return asList(strategies);
+ }
+
+ private ExtensionLoader(Class> type) {
+ this.type = type;
+ objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
+ }
+
+ private static boolean withExtensionAnnotation(Class type) {
+ return type.isAnnotationPresent(SPI.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static ExtensionLoader getExtensionLoader(Class type) {
+ if (type == null) {
+ throw new IllegalArgumentException("Extension type == null");
+ }
+ if (!type.isInterface()) {
+ throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
+ }
+ if (!withExtensionAnnotation(type)) {
+ throw new IllegalArgumentException("Extension type (" + type +
+ ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
+ }
+
+ ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
+ if (loader == null) {
+ EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
+ loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
+ }
+ return loader;
+ }
+
+ // For testing purposes only
+ public static void resetExtensionLoader(Class type) {
+ ExtensionLoader loader = EXTENSION_LOADERS.get(type);
+ if (loader != null) {
+ // Remove all instances associated with this loader as well
+ Map> classes = loader.getExtensionClasses();
+ for (Map.Entry> entry : classes.entrySet()) {
+ EXTENSION_INSTANCES.remove(entry.getValue());
+ }
+ classes.clear();
+ EXTENSION_LOADERS.remove(type);
+ }
+ }
+
+ public static void destroyAll() {
+ EXTENSION_INSTANCES.forEach((_type, instance) -> {
+ if (instance instanceof Lifecycle) {
+ Lifecycle lifecycle = (Lifecycle) instance;
+ try {
+ lifecycle.destroy();
+ } catch (Exception e) {
+ logger.error("Error destroying extension " + lifecycle, e);
+ }
+ }
+ });
+ }
+
+ private static ClassLoader findClassLoader() {
+ return ClassUtils.getClassLoader(ExtensionLoader.class);
+ }
+
+ public String getExtensionName(T extensionInstance) {
+ return getExtensionName(extensionInstance.getClass());
+ }
+
+ public String getExtensionName(Class> extensionClass) {
+ getExtensionClasses();// load class
+ return cachedNames.get(extensionClass);
+ }
+
+ /**
+ * This is equivalent to {@code getActivateExtension(url, key, null)}
+ *
+ * @param url url
+ * @param key url parameter key which used to get extension point names
+ * @return extension list which are activated.
+ * @see #getActivateExtension(org.apache.dubbo.common.URL, String, String)
+ */
+ public List getActivateExtension(URL url, String key) {
+ return getActivateExtension(url, key, null);
+ }
+
+ /**
+ * This is equivalent to {@code getActivateExtension(url, values, null)}
+ *
+ * @param url url
+ * @param values extension point names
+ * @return extension list which are activated
+ * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
+ */
+ public List getActivateExtension(URL url, String[] values) {
+ return getActivateExtension(url, values, null);
+ }
+
+ /**
+ * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), null)}
+ *
+ * @param url url
+ * @param key url parameter key which used to get extension point names
+ * @param group group
+ * @return extension list which are activated.
+ * @see #getActivateExtension(org.apache.dubbo.common.URL, String[], String)
+ */
+ public List getActivateExtension(URL url, String key, String group) {
+ String value = url.getParameter(key);
+ return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
+ }
+
+ /**
+ * Get activate extensions.
+ *
+ * @param url url
+ * @param values extension point names
+ * @param group group
+ * @return extension list which are activated
+ * @see org.apache.dubbo.common.extension.Activate
+ */
+ public List getActivateExtension(URL url, String[] values, String group) {
+ List activateExtensions = new ArrayList<>();
+ List names = values == null ? new ArrayList<>(0) : asList(values);
+ if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
+ getExtensionClasses();
+ for (Map.Entry entry : cachedActivates.entrySet()) {
+ String name = entry.getKey();
+ Object activate = entry.getValue();
+
+ String[] activateGroup, activateValue;
+
+ if (activate instanceof Activate) {
+ activateGroup = ((Activate) activate).group();
+ activateValue = ((Activate) activate).value();
+ } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
+ activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
+ activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
+ } else {
+ continue;
+ }
+ if (isMatchGroup(group, activateGroup)
+ && !names.contains(name)
+ && !names.contains(REMOVE_VALUE_PREFIX + name)
+ && isActive(activateValue, url)) {
+ activateExtensions.add(getExtension(name));
+ }
+ }
+ activateExtensions.sort(ActivateComparator.COMPARATOR);
+ }
+ List loadedExtensions = new ArrayList<>();
+ for (int i = 0; i < names.size(); i++) {
+ String name = names.get(i);
+ if (!name.startsWith(REMOVE_VALUE_PREFIX)
+ && !names.contains(REMOVE_VALUE_PREFIX + name)) {
+ if (DEFAULT_KEY.equals(name)) {
+ if (!loadedExtensions.isEmpty()) {
+ activateExtensions.addAll(0, loadedExtensions);
+ loadedExtensions.clear();
+ }
+ } else {
+ loadedExtensions.add(getExtension(name));
+ }
+ }
+ }
+ if (!loadedExtensions.isEmpty()) {
+ activateExtensions.addAll(loadedExtensions);
+ }
+ return activateExtensions;
+ }
+
+ private boolean isMatchGroup(String group, String[] groups) {
+ if (StringUtils.isEmpty(group)) {
+ return true;
+ }
+ if (groups != null && groups.length > 0) {
+ for (String g : groups) {
+ if (group.equals(g)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isActive(String[] keys, URL url) {
+ if (keys.length == 0) {
+ return true;
+ }
+ for (String key : keys) {
+ // @Active(value="key1:value1, key2:value2")
+ String keyValue = null;
+ if (key.contains(":")) {
+ String[] arr = key.split(":");
+ key = arr[0];
+ keyValue = arr[1];
+ }
+
+ for (Map.Entry entry : url.getParameters().entrySet()) {
+ String k = entry.getKey();
+ String v = entry.getValue();
+ if ((k.equals(key) || k.endsWith("." + key))
+ && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get extension's instance. Return null
if extension is not found or is not initialized. Pls. note
+ * that this method will not trigger extension load.
+ *
+ * In order to trigger extension load, call {@link #getExtension(String)} instead.
+ *
+ * @see #getExtension(String)
+ */
+ @SuppressWarnings("unchecked")
+ public T getLoadedExtension(String name) {
+ if (StringUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("Extension name == null");
+ }
+ Holder holder = getOrCreateHolder(name);
+ return (T) holder.get();
+ }
+
+ private Holder getOrCreateHolder(String name) {
+ Holder holder = cachedInstances.get(name);
+ if (holder == null) {
+ cachedInstances.putIfAbsent(name, new Holder<>());
+ holder = cachedInstances.get(name);
+ }
+ return holder;
+ }
+
+ /**
+ * Return the list of extensions which are already loaded.
+ *
+ * Usually {@link #getSupportedExtensions()} should be called in order to get all extensions.
+ *
+ * @see #getSupportedExtensions()
+ */
+ public Set getLoadedExtensions() {
+ return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
+ }
+
+ public List getLoadedExtensionInstances() {
+ List instances = new ArrayList<>();
+ cachedInstances.values().forEach(holder -> instances.add((T) holder.get()));
+ return instances;
+ }
+
+ public Object getLoadedAdaptiveExtensionInstances() {
+ return cachedAdaptiveInstance.get();
+ }
+
+// public T getPrioritizedExtensionInstance() {
+// Set supported = getSupportedExtensions();
+//
+// Set instances = new HashSet<>();
+// Set prioritized = new HashSet<>();
+// for (String s : supported) {
+//
+// }
+//
+// }
+
+ /**
+ * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException}
+ * will be thrown.
+ */
+ @SuppressWarnings("unchecked")
+ public T getExtension(String name) {
+ return getExtension(name, true);
+ }
+
+ public T getExtension(String name, boolean wrap) {
+ if (StringUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("Extension name == null");
+ }
+ if ("true".equals(name)) {
+ return getDefaultExtension();
+ }
+ final Holder holder = getOrCreateHolder(name);
+ Object instance = holder.get();
+ if (instance == null) {
+ synchronized (holder) {
+ instance = holder.get();
+ if (instance == null) {
+ instance = createExtension(name, wrap);
+ holder.set(instance);
+ }
+ }
+ }
+ return (T) instance;
+ }
+
+ /**
+ * Get the extension by specified name if found, or {@link #getDefaultExtension() returns the default one}
+ *
+ * @param name the name of extension
+ * @return non-null
+ */
+ public T getOrDefaultExtension(String name) {
+ return containsExtension(name) ? getExtension(name) : getDefaultExtension();
+ }
+
+ /**
+ * Return default extension, return null
if it's not configured.
+ */
+ public T getDefaultExtension() {
+ getExtensionClasses();
+ if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
+ return null;
+ }
+ return getExtension(cachedDefaultName);
+ }
+
+ public boolean hasExtension(String name) {
+ if (StringUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("Extension name == null");
+ }
+ Class> c = this.getExtensionClass(name);
+ return c != null;
+ }
+
+ public Set getSupportedExtensions() {
+ Map> clazzes = getExtensionClasses();
+ return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
+ }
+
+ public Set getSupportedExtensionInstances() {
+ List instances = new LinkedList<>();
+ Set supportedExtensions = getSupportedExtensions();
+ if (CollectionUtils.isNotEmpty(supportedExtensions)) {
+ for (String name : supportedExtensions) {
+ instances.add(getExtension(name));
+ }
+ }
+ // sort the Prioritized instances
+ sort(instances, Prioritized.COMPARATOR);
+ return new LinkedHashSet<>(instances);
+ }
+
+ /**
+ * Return default extension name, return null
if not configured.
+ */
+ public String getDefaultExtensionName() {
+ getExtensionClasses();
+ return cachedDefaultName;
+ }
+
+ /**
+ * Register new extension via API
+ *
+ * @param name extension name
+ * @param clazz extension class
+ * @throws IllegalStateException when extension with the same name has already been registered.
+ */
+ public void addExtension(String name, Class> clazz) {
+ getExtensionClasses(); // load classes
+
+ if (!type.isAssignableFrom(clazz)) {
+ throw new IllegalStateException("Input type " +
+ clazz + " doesn't implement the Extension " + type);
+ }
+ if (clazz.isInterface()) {
+ throw new IllegalStateException("Input type " +
+ clazz + " can't be interface!");
+ }
+
+ if (!clazz.isAnnotationPresent(Adaptive.class)) {
+ if (StringUtils.isBlank(name)) {
+ throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
+ }
+ if (cachedClasses.get().containsKey(name)) {
+ throw new IllegalStateException("Extension name " +
+ name + " already exists (Extension " + type + ")!");
+ }
+
+ cachedNames.put(clazz, name);
+ cachedClasses.get().put(name, clazz);
+ } else {
+ if (cachedAdaptiveClass != null) {
+ throw new IllegalStateException("Adaptive Extension already exists (Extension " + type + ")!");
+ }
+
+ cachedAdaptiveClass = clazz;
+ }
+ }
+
+ /**
+ * Replace the existing extension via API
+ *
+ * @param name extension name
+ * @param clazz extension class
+ * @throws IllegalStateException when extension to be placed doesn't exist
+ * @deprecated not recommended any longer, and use only when test
+ */
+ @Deprecated
+ public void replaceExtension(String name, Class> clazz) {
+ getExtensionClasses(); // load classes
+
+ if (!type.isAssignableFrom(clazz)) {
+ throw new IllegalStateException("Input type " +
+ clazz + " doesn't implement Extension " + type);
+ }
+ if (clazz.isInterface()) {
+ throw new IllegalStateException("Input type " +
+ clazz + " can't be interface!");
+ }
+
+ if (!clazz.isAnnotationPresent(Adaptive.class)) {
+ if (StringUtils.isBlank(name)) {
+ throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
+ }
+ if (!cachedClasses.get().containsKey(name)) {
+ throw new IllegalStateException("Extension name " +
+ name + " doesn't exist (Extension " + type + ")!");
+ }
+
+ cachedNames.put(clazz, name);
+ cachedClasses.get().put(name, clazz);
+ cachedInstances.remove(name);
+ } else {
+ if (cachedAdaptiveClass == null) {
+ throw new IllegalStateException("Adaptive Extension doesn't exist (Extension " + type + ")!");
+ }
+
+ cachedAdaptiveClass = clazz;
+ cachedAdaptiveInstance.set(null);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public T getAdaptiveExtension() {
+ Object instance = cachedAdaptiveInstance.get();
+ if (instance == null) {
+ if (createAdaptiveInstanceError != null) {
+ throw new IllegalStateException("Failed to create adaptive instance: " +
+ createAdaptiveInstanceError.toString(),
+ createAdaptiveInstanceError);
+ }
+
+ synchronized (cachedAdaptiveInstance) {
+ instance = cachedAdaptiveInstance.get();
+ if (instance == null) {
+ try {
+ instance = createAdaptiveExtension();
+ cachedAdaptiveInstance.set(instance);
+ } catch (Throwable t) {
+ createAdaptiveInstanceError = t;
+ throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
+ }
+ }
+ }
+ }
+
+ return (T) instance;
+ }
+
+ private IllegalStateException findException(String name) {
+ for (Map.Entry entry : exceptions.entrySet()) {
+ if (entry.getKey().toLowerCase().contains(name.toLowerCase())) {
+ return entry.getValue();
+ }
+ }
+ StringBuilder buf = new StringBuilder("No such extension " + type.getName() + " by name " + name);
+
+
+ int i = 1;
+ for (Map.Entry entry : exceptions.entrySet()) {
+ if (i == 1) {
+ buf.append(", possible causes: ");
+ }
+
+ buf.append("\r\n(");
+ buf.append(i++);
+ buf.append(") ");
+ buf.append(entry.getKey());
+ buf.append(":\r\n");
+ buf.append(StringUtils.toString(entry.getValue()));
+ }
+ return new IllegalStateException(buf.toString());
+ }
+
+ @SuppressWarnings("unchecked")
+ private T createExtension(String name, boolean wrap) {
+ Class> clazz = getExtensionClasses().get(name);
+ if (clazz == null) {
+ throw findException(name);
+ }
+ try {
+ T instance = (T) EXTENSION_INSTANCES.get(clazz);
+ if (instance == null) {
+ EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
+ instance = (T) EXTENSION_INSTANCES.get(clazz);
+ }
+ injectExtension(instance);
+
+
+ if (wrap) {
+
+ List> wrapperClassesList = new ArrayList<>();
+ if (cachedWrapperClasses != null) {
+ wrapperClassesList.addAll(cachedWrapperClasses);
+ wrapperClassesList.sort(WrapperComparator.COMPARATOR);
+ Collections.reverse(wrapperClassesList);
+ }
+
+ if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
+ for (Class> wrapperClass : wrapperClassesList) {
+ Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
+ if (wrapper == null
+ || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
+ instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
+ }
+ }
+ }
+ }
+
+ initExtension(instance);
+ return instance;
+ } catch (Throwable t) {
+ throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
+ type + ") couldn't be instantiated: " + t.getMessage(), t);
+ }
+ }
+
+ private boolean containsExtension(String name) {
+ return getExtensionClasses().containsKey(name);
+ }
+
+ private T injectExtension(T instance) {
+
+ if (objectFactory == null) {
+ return instance;
+ }
+
+ try {
+ for (Method method : instance.getClass().getMethods()) {
+ if (!isSetter(method)) {
+ continue;
+ }
+ /**
+ * Check {@link DisableInject} to see if we need auto injection for this property
+ */
+ if (method.getAnnotation(DisableInject.class) != null) {
+ continue;
+ }
+ Class> pt = method.getParameterTypes()[0];
+ if (ReflectUtils.isPrimitives(pt)) {
+ continue;
+ }
+
+ try {
+ String property = getSetterProperty(method);
+ Object object = objectFactory.getExtension(pt, property);
+ if (object != null) {
+ method.invoke(instance, object);
+ }
+ } catch (Exception e) {
+ logger.error("Failed to inject via method " + method.getName()
+ + " of interface " + type.getName() + ": " + e.getMessage(), e);
+ }
+
+ }
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ return instance;
+ }
+
+ private void initExtension(T instance) {
+ if (instance instanceof Lifecycle) {
+ Lifecycle lifecycle = (Lifecycle) instance;
+ lifecycle.initialize();
+ }
+ }
+
+ /**
+ * get properties name for setter, for instance: setVersion, return "version"
+ *
+ * return "", if setter name with length less than 3
+ */
+ private String getSetterProperty(Method method) {
+ return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
+ }
+
+ /**
+ * return true if and only if:
+ *
+ * 1, public
+ *
+ * 2, name starts with "set"
+ *
+ * 3, only has one parameter
+ */
+ private boolean isSetter(Method method) {
+ return method.getName().startsWith("set")
+ && method.getParameterTypes().length == 1
+ && Modifier.isPublic(method.getModifiers());
+ }
+
+ private Class> getExtensionClass(String name) {
+ if (type == null) {
+ throw new IllegalArgumentException("Extension type == null");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("Extension name == null");
+ }
+ return getExtensionClasses().get(name);
+ }
+
+ private Map> getExtensionClasses() {
+ Map> classes = cachedClasses.get();
+ if (classes == null) {
+ synchronized (cachedClasses) {
+ classes = cachedClasses.get();
+ if (classes == null) {
+ classes = loadExtensionClasses();
+ cachedClasses.set(classes);
+ }
+ }
+ }
+ return classes;
+ }
+
+ /**
+ * synchronized in getExtensionClasses
+ */
+ private Map> loadExtensionClasses() {
+ cacheDefaultExtensionName();
+
+ Map> extensionClasses = new HashMap<>();
+
+ for (LoadingStrategy strategy : strategies) {
+ loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
+ loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
+ }
+
+ return extensionClasses;
+ }
+
+ /**
+ * extract and cache default extension name if exists
+ */
+ private void cacheDefaultExtensionName() {
+ final SPI defaultAnnotation = type.getAnnotation(SPI.class);
+ if (defaultAnnotation == null) {
+ return;
+ }
+
+ String value = defaultAnnotation.value();
+ if ((value = value.trim()).length() > 0) {
+ String[] names = NAME_SEPARATOR.split(value);
+ if (names.length > 1) {
+ throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ + ": " + Arrays.toString(names));
+ }
+ if (names.length == 1) {
+ cachedDefaultName = names[0];
+ }
+ }
+ }
+
+ private void loadDirectory(Map> extensionClasses, String dir, String type) {
+ loadDirectory(extensionClasses, dir, type, false, false);
+ }
+
+ private void loadDirectory(Map> extensionClasses, String dir, String type,
+ boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
+ String fileName = dir + type;
+ try {
+ Enumeration urls = null;
+ ClassLoader classLoader = findClassLoader();
+
+ // try to load from ExtensionLoader's ClassLoader first
+ if (extensionLoaderClassLoaderFirst) {
+ ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
+ if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
+ urls = extensionLoaderClassLoader.getResources(fileName);
+ }
+ }
+
+ if (urls == null || !urls.hasMoreElements()) {
+ if (classLoader != null) {
+ urls = classLoader.getResources(fileName);
+ } else {
+ urls = ClassLoader.getSystemResources(fileName);
+ }
+ }
+
+ if (urls != null) {
+ while (urls.hasMoreElements()) {
+ java.net.URL resourceURL = urls.nextElement();
+ loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
+ }
+ }
+ } catch (Throwable t) {
+ logger.error("Exception occurred when loading extension class (interface: " +
+ type + ", description file: " + fileName + ").", t);
+ }
+ }
+
+ private void loadResource(Map> extensionClasses, ClassLoader classLoader,
+ java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
+ try {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ final int ci = line.indexOf('#');
+ if (ci >= 0) {
+ line = line.substring(0, ci);
+ }
+ line = line.trim();
+ if (line.length() > 0) {
+ try {
+ String name = null;
+ int i = line.indexOf('=');
+ if (i > 0) {
+ name = line.substring(0, i).trim();
+ line = line.substring(i + 1).trim();
+ }
+ if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
+ loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
+ }
+ } catch (Throwable t) {
+ IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
+ exceptions.put(line, e);
+ }
+ }
+ }
+ }
+ } catch (Throwable t) {
+ logger.error("Exception occurred when loading extension class (interface: " +
+ type + ", class file: " + resourceURL + ") in " + resourceURL, t);
+ }
+ }
+
+ private boolean isExcluded(String className, String... excludedPackages) {
+ if (excludedPackages != null) {
+ for (String excludePackage : excludedPackages) {
+ if (className.startsWith(excludePackage + ".")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name,
+ boolean overridden) throws NoSuchMethodException {
+ if (!type.isAssignableFrom(clazz)) {
+ throw new IllegalStateException("Error occurred when loading extension class (interface: " +
+ type + ", class line: " + clazz.getName() + "), class "
+ + clazz.getName() + " is not subtype of interface.");
+ }
+ if (clazz.isAnnotationPresent(Adaptive.class)) {
+ cacheAdaptiveClass(clazz, overridden);
+ } else if (isWrapperClass(clazz)) {
+ cacheWrapperClass(clazz);
+ } else {
+ clazz.getConstructor();
+ if (StringUtils.isEmpty(name)) {
+ name = findAnnotationName(clazz);
+ if (name.length() == 0) {
+ throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
+ }
+ }
+
+ String[] names = NAME_SEPARATOR.split(name);
+ if (ArrayUtils.isNotEmpty(names)) {
+ cacheActivateClass(clazz, names[0]);
+ for (String n : names) {
+ cacheName(clazz, n);
+ saveInExtensionClass(extensionClasses, clazz, n, overridden);
+ }
+ }
+ }
+ }
+
+ /**
+ * cache name
+ */
+ private void cacheName(Class> clazz, String name) {
+ if (!cachedNames.containsKey(clazz)) {
+ cachedNames.put(clazz, name);
+ }
+ }
+
+ /**
+ * put clazz in extensionClasses
+ */
+ private void saveInExtensionClass(Map> extensionClasses, Class> clazz, String name, boolean overridden) {
+ Class> c = extensionClasses.get(name);
+ if (c == null || overridden) {
+ extensionClasses.put(name, clazz);
+ } else if (c != clazz) {
+ String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
+ logger.error(duplicateMsg);
+ throw new IllegalStateException(duplicateMsg);
+ }
+ }
+
+ /**
+ * cache Activate class which is annotated with Activate
+ *
+ * for compatibility, also cache class with old alibaba Activate annotation
+ */
+ private void cacheActivateClass(Class> clazz, String name) {
+ Activate activate = clazz.getAnnotation(Activate.class);
+ if (activate != null) {
+ cachedActivates.put(name, activate);
+ } else {
+ // support com.alibaba.dubbo.common.extension.Activate
+ com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
+ if (oldActivate != null) {
+ cachedActivates.put(name, oldActivate);
+ }
+ }
+ }
+
+ /**
+ * cache Adaptive class which is annotated with Adaptive
+ */
+ private void cacheAdaptiveClass(Class> clazz, boolean overridden) {
+ if (cachedAdaptiveClass == null || overridden) {
+ cachedAdaptiveClass = clazz;
+ } else if (!cachedAdaptiveClass.equals(clazz)) {
+ throw new IllegalStateException("More than 1 adaptive class found: "
+ + cachedAdaptiveClass.getName()
+ + ", " + clazz.getName());
+ }
+ }
+
+ /**
+ * cache wrapper class
+ *
+ * like: ProtocolFilterWrapper, ProtocolListenerWrapper
+ */
+ private void cacheWrapperClass(Class> clazz) {
+ if (cachedWrapperClasses == null) {
+ cachedWrapperClasses = new ConcurrentHashSet<>();
+ }
+ cachedWrapperClasses.add(clazz);
+ }
+
+ /**
+ * test if clazz is a wrapper class
+ *
+ * which has Constructor with given class type as its only argument
+ */
+ private boolean isWrapperClass(Class> clazz) {
+ try {
+ clazz.getConstructor(type);
+ return true;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private String findAnnotationName(Class> clazz) {
+ org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
+ if (extension != null) {
+ return extension.value();
+ }
+
+ String name = clazz.getSimpleName();
+ if (name.endsWith(type.getSimpleName())) {
+ name = name.substring(0, name.length() - type.getSimpleName().length());
+ }
+ return name.toLowerCase();
+ }
+
+ @SuppressWarnings("unchecked")
+ private T createAdaptiveExtension() {
+ try {
+ return injectExtension((T) getAdaptiveExtensionClass().newInstance());
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
+ }
+ }
+
+ private Class> getAdaptiveExtensionClass() {
+ getExtensionClasses();
+ if (cachedAdaptiveClass != null) {
+ return cachedAdaptiveClass;
+ }
+ return cachedAdaptiveClass = createAdaptiveExtensionClass();
+ }
+
+ private Class> createAdaptiveExtensionClass() {
+ String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
+ ClassLoader classLoader = findClassLoader();
+ org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
+ return compiler.compile(code, classLoader);
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getName() + "[" + type.getName() + "]";
+ }
+
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/Wrapper.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/Wrapper.java
new file mode 100644
index 00000000000..670aae6de05
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/Wrapper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.common.extension;
+
+/**
+ * The annotated class will only work as a wrapper when the condition matches.
+ */
+public @interface Wrapper {
+
+ /**
+ * the extension names that need to be wrapped.
+ */
+ String[] matches() default {};
+
+ /**
+ * the extension names that need to be excluded.
+ */
+ String[] mismatches() default {};
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
index 1114804bd69..9ad1aaf5438 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/ActivateComparator.java
@@ -75,10 +75,8 @@ public int compare(Object o1, Object o2) {
}
}
}
- int n1 = a1 == null ? 0 : a1.order;
- int n2 = a2 == null ? 0 : a2.order;
// never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
- return n1 > n2 ? 1 : -1;
+ return a1.order > a2.order ? 1 : -1;
}
private Class> findSpi(Class clazz) {
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/WrapperComparator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/WrapperComparator.java
new file mode 100644
index 00000000000..ead4a4f6e45
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/support/WrapperComparator.java
@@ -0,0 +1,95 @@
+/*
+ * 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.common.extension.support;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.extension.SPI;
+
+import java.util.Comparator;
+
+/**
+ * OrderComparator
+ */
+public class WrapperComparator implements Comparator {
+
+ public static final Comparator COMPARATOR = new WrapperComparator();
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1 == null && o2 == null) {
+ return 0;
+ }
+ if (o1 == null) {
+ return -1;
+ }
+ if (o2 == null) {
+ return 1;
+ }
+ if (o1.equals(o2)) {
+ return 0;
+ }
+
+ Class clazz1 = (Class) o1;
+ Class clazz2 = (Class) o2;
+
+ Class> inf = findSpi(clazz1);
+
+ OrderInfo a1 = parseOrder(clazz1);
+ OrderInfo a2 = parseOrder(clazz2);
+
+ int n1 = a1 == null ? 0 : a1.order;
+ int n2 = a2 == null ? 0 : a2.order;
+ // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
+ return n1 > n2 ? 1 : -1;
+ }
+
+ private Class> findSpi(Class clazz) {
+ if (clazz.getInterfaces().length == 0) {
+ return null;
+ }
+
+ for (Class> intf : clazz.getInterfaces()) {
+ if (intf.isAnnotationPresent(SPI.class)) {
+ return intf;
+ } else {
+ Class result = findSpi(intf);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private OrderInfo parseOrder(Class> clazz) {
+ OrderInfo info = new OrderInfo();
+ if (clazz.isAnnotationPresent(Activate.class)) {
+ Activate activate = clazz.getAnnotation(Activate.class);
+ info.order = activate.order();
+ } else if (clazz.isAnnotationPresent(com.alibaba.dubbo.common.extension.Activate.class)) {
+ com.alibaba.dubbo.common.extension.Activate activate = clazz.getAnnotation(
+ com.alibaba.dubbo.common.extension.Activate.class);
+ info.order = activate.order();
+ }
+ return info;
+ }
+
+ private static class OrderInfo {
+ private int order;
+ }
+}
\ No newline at end of file
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
index a0b62bd402c..5be000d78f4 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ArrayUtils.java
@@ -44,4 +44,26 @@ public static boolean isEmpty(final Object[] array) {
public static boolean isNotEmpty(final Object[] array) {
return !isEmpty(array);
}
+
+ public static boolean contains(final String[] array, String valueToFind) {
+ return indexOf(array, valueToFind, 0) != -1;
+ }
+
+ public static int indexOf(String[] array, String valueToFind, int startIndex) {
+ if (isEmpty(array) || valueToFind == null) {
+ return -1;
+ } else {
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ for (int i = startIndex; i < array.length; ++i) {
+ if (valueToFind.equals(array[i])) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+ }
}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
index 24f51766529..f1b14d6df62 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ServiceConfigBase.java
@@ -362,15 +362,15 @@ public void setGeneric(String generic) {
}
}
- @Override
- public void setMock(String mock) {
- throw new IllegalArgumentException("mock doesn't support on provider side");
- }
-
- @Override
- public void setMock(Object mock) {
- throw new IllegalArgumentException("mock doesn't support on provider side");
- }
+// @Override
+// public void setMock(String mock) {
+// throw new IllegalArgumentException("mock doesn't support on provider side");
+// }
+//
+// @Override
+// public void setMock(Object mock) {
+// throw new IllegalArgumentException("mock doesn't support on provider side");
+// }
public ServiceMetadata getServiceMetadata() {
return serviceMetadata;
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
index 07647bf3cbf..4bc1660aceb 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java
@@ -67,10 +67,10 @@ public class ConfigManager extends LifecycleAdapter implements FrameworkExt {
public static final String NAME = "config";
- private final Map> configsCache = newMap();
-
private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ final Map> configsCache = newMap();
+
public ConfigManager() {
}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java
index 9dca2e895f9..e49888d12c3 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ConsumerModel.java
@@ -138,7 +138,12 @@ public void setServiceKey(String serviceKey) {
}
public void initMethodModels() {
- Class[] interfaceList = serviceMetadata.getTarget().getClass().getInterfaces();
+ Class[] interfaceList = null;
+ if (proxyObject == null) {
+ interfaceList = new Class[]{referenceConfig.getActualInterface()};
+ } else {
+ interfaceList = proxyObject.getClass().getInterfaces();
+ }
for (Class interfaceClass : interfaceList) {
for (Method method : interfaceClass.getMethods()) {
methodModels.put(method, new ConsumerMethodModel(method));
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
index db5f57b1c51..dc965959c16 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java
@@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.Predicate;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
@@ -68,10 +69,10 @@ public void test_valueOf_noProtocolAndHost() throws Exception {
private void assertURLStrDecoder(URL url) {
String fullURLStr = url.toFullString();
- URL newUrl = URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
+ URL newUrl = URLStrParser.parseEncodedStr(URL.encode(fullURLStr));
assertEquals(URL.valueOf(fullURLStr), newUrl);
- URL newUrl2 = URLStrParser.parseDecodedStr(fullURLStr);
+ URL newUrl2 = URLStrParser.parseDecodedStr(fullURLStr);
assertEquals(URL.valueOf(fullURLStr), newUrl2);
}
@@ -874,4 +875,19 @@ public void testValueOf() {
url = URL.valueOf("dubbo://10.20.130.230:20880/path");
assertURLStrDecoder(url);
}
+
+
+ /**
+ * Test {@link URL#getParameters(Predicate)} method
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testGetParameters() {
+ URL url = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0");
+ Map parameters = url.getParameters(i -> "version".equals(i));
+ String version = parameters.get("version");
+ assertEquals(1, parameters.size());
+ assertEquals("1.0.0", version);
+ }
}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java
index 772727b80db..7605a3c6184 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/AbstractDynamicConfigurationTest.java
@@ -28,10 +28,13 @@
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME;
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.DEFAULT_THREAD_POOL_PREFIX;
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.DEFAULT_THREAD_POOL_SIZE;
+import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.GROUP_PARAM_NAME;
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.PARAM_NAME_PREFIX;
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.THREAD_POOL_KEEP_ALIVE_TIME_PARAM_NAME;
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.THREAD_POOL_PREFIX_PARAM_NAME;
import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.THREAD_POOL_SIZE_PARAM_NAME;
+import static org.apache.dubbo.common.config.configcenter.AbstractDynamicConfiguration.TIMEOUT_PARAM_NAME;
+import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -75,6 +78,10 @@ public void testConstants() {
assertEquals("dubbo.config-center.thread-pool.keep-alive-time", THREAD_POOL_KEEP_ALIVE_TIME_PARAM_NAME);
assertEquals(1, DEFAULT_THREAD_POOL_SIZE);
assertEquals(60 * 1000, DEFAULT_THREAD_POOL_KEEP_ALIVE_TIME);
+
+ // @since 2.7.8
+ assertEquals("dubbo.config-center.group", GROUP_PARAM_NAME);
+ assertEquals("dubbo.config-center.timeout", TIMEOUT_PARAM_NAME);
}
@Test
@@ -158,4 +165,42 @@ public void testRemoveListener() {
public void testClose() throws Exception {
configuration.close();
}
+
+ /**
+ * Test {@link AbstractDynamicConfiguration#getGroup()} and
+ * {@link AbstractDynamicConfiguration#getDefaultGroup()} methods
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testGetGroupAndGetDefaultGroup() {
+ assertEquals(configuration.getGroup(), configuration.getDefaultGroup());
+ assertEquals(DEFAULT_GROUP, configuration.getDefaultGroup());
+ }
+
+ /**
+ * Test {@link AbstractDynamicConfiguration#getTimeout()} and
+ * {@link AbstractDynamicConfiguration#getDefaultTimeout()} methods
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testGetTimeoutAndGetDefaultTimeout() {
+ assertEquals(configuration.getTimeout(), configuration.getDefaultTimeout());
+ assertEquals(-1L, configuration.getDefaultTimeout());
+ }
+
+ /**
+ * Test {@link AbstractDynamicConfiguration#removeConfig(String, String)} and
+ * {@link AbstractDynamicConfiguration#doRemoveConfig(String, String)} methods
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testRemoveConfigAndDoRemoveConfig() throws Exception {
+ String key = null;
+ String group = null;
+ assertEquals(configuration.removeConfig(key, group), configuration.doRemoveConfig(key, group));
+ assertFalse(configuration.removeConfig(key, group));
+ }
}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
index c471bb16182..80282c185c3 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/config/configcenter/file/FileSystemDynamicConfigurationTest.java
@@ -17,6 +17,8 @@
package org.apache.dubbo.common.config.configcenter.file;
import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterEach;
@@ -24,15 +26,17 @@
import org.junit.jupiter.api.Test;
import java.io.File;
+import java.util.TreeSet;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
+import static java.util.Arrays.asList;
import static org.apache.commons.io.FileUtils.deleteQuietly;
import static org.apache.dubbo.common.URL.valueOf;
import static org.apache.dubbo.common.config.configcenter.DynamicConfiguration.DEFAULT_GROUP;
import static org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration.CONFIG_CENTER_DIR_PARAM_NAME;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -41,6 +45,8 @@
*/
public class FileSystemDynamicConfigurationTest {
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
private FileSystemDynamicConfiguration configuration;
private static final String KEY = "abc-def-ghi";
@@ -53,11 +59,11 @@ public void init() {
rootDirectory.mkdirs();
URL url = valueOf("dubbo://127.0.0.1:20880").addParameter(CONFIG_CENTER_DIR_PARAM_NAME, rootDirectory.getAbsolutePath());
configuration = new FileSystemDynamicConfiguration(url);
- deleteQuietly(configuration.getRootDirectory());
}
@AfterEach
public void destroy() throws Exception {
+ deleteQuietly(configuration.getRootDirectory());
configuration.close();
}
@@ -73,9 +79,6 @@ public void testInit() {
assertEquals(ThreadPoolExecutor.class, configuration.getWorkersThreadPool().getClass());
assertEquals(1, (configuration.getWorkersThreadPool()).getCorePoolSize());
assertEquals(1, (configuration.getWorkersThreadPool()).getMaximumPoolSize());
- assertNotNull(configuration.getWatchEventsLoopThreadPool());
- assertEquals(1, (configuration.getWatchEventsLoopThreadPool()).getCorePoolSize());
- assertEquals(1, (configuration.getWatchEventsLoopThreadPool()).getMaximumPoolSize());
if (configuration.isBasedPoolingWatchService()) {
assertEquals(2, configuration.getDelay());
@@ -103,7 +106,7 @@ public void testAddAndRemoveListener() throws InterruptedException {
processedEvent.set(true);
assertEquals(KEY, event.getKey());
- System.out.printf("[%s] " + event + "\n", Thread.currentThread().getName());
+ logger.info(String.format("[%s] " + event + "\n", Thread.currentThread().getName()));
});
@@ -127,7 +130,7 @@ public void testAddAndRemoveListener() throws InterruptedException {
configuration.addListener("test", "test", event -> {
processedEvent.set(true);
assertEquals("test", event.getKey());
- System.out.printf("[%s] " + event + "\n", Thread.currentThread().getName());
+ logger.info(String.format("[%s] " + event + "\n", Thread.currentThread().getName()));
});
processedEvent.set(false);
configuration.publishConfig("test", "test", "TEST");
@@ -148,4 +151,29 @@ public void testAddAndRemoveListener() throws InterruptedException {
Thread.sleep(1 * 1000L);
}
}
+
+ @Test
+ public void testRemoveConfig() throws Exception {
+
+ assertTrue(configuration.publishConfig(KEY, DEFAULT_GROUP, "A"));
+
+ assertEquals("A", FileUtils.readFileToString(configuration.configFile(KEY, DEFAULT_GROUP), configuration.getEncoding()));
+
+ assertTrue(configuration.removeConfig(KEY, DEFAULT_GROUP));
+
+ assertFalse(configuration.configFile(KEY, DEFAULT_GROUP).exists());
+
+ }
+
+ @Test
+ public void testGetConfigKeys() throws Exception {
+
+ assertTrue(configuration.publishConfig("A", DEFAULT_GROUP, "A"));
+
+ assertTrue(configuration.publishConfig("B", DEFAULT_GROUP, "B"));
+
+ assertTrue(configuration.publishConfig("C", DEFAULT_GROUP, "C"));
+
+ assertEquals(new TreeSet(asList("A", "B", "C")), configuration.getConfigKeys(DEFAULT_GROUP));
+ }
}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.java
new file mode 100644
index 00000000000..bc85dfb3d50
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/constants/CommonConstantsTest.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.common.constants;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR_CHAR;
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH;
+import static org.apache.dubbo.common.constants.CommonConstants.SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CommonConstants} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class CommonConstantsTest {
+
+ @Test
+ public void test() {
+ assertEquals(',', COMMA_SEPARATOR_CHAR);
+ assertEquals("composite", COMPOSITE_METADATA_STORAGE_TYPE);
+ assertEquals("service-name-mapping.properties-path", SERVICE_NAME_MAPPING_PROPERTIES_FILE_KEY);
+ assertEquals("META-INF/dubbo/service-name-mapping.properties", DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH);
+ }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java
index 8422c781090..0d38e6c8c54 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java
@@ -24,10 +24,15 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import static java.util.Arrays.asList;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
+import static org.apache.dubbo.common.utils.StringUtils.splitToList;
+import static org.apache.dubbo.common.utils.StringUtils.splitToSet;
import static org.apache.dubbo.common.utils.StringUtils.toCommaDelimitedString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@@ -243,16 +248,32 @@ public void testSplit() throws Exception {
public void testSplitToList() throws Exception {
String str = "d,1,2,4";
- assertEquals(4, StringUtils.splitToList(str, ',').size());
- assertEquals(Arrays.asList(str.split(",")), StringUtils.splitToList(str, ','));
+ assertEquals(4, splitToList(str, ',').size());
+ assertEquals(asList(str.split(",")), splitToList(str, ','));
- assertEquals(1, StringUtils.splitToList(str, 'a').size());
- assertEquals(Arrays.asList(str.split("a")), StringUtils.splitToList(str, 'a'));
+ assertEquals(1, splitToList(str, 'a').size());
+ assertEquals(asList(str.split("a")), splitToList(str, 'a'));
- assertEquals(0, StringUtils.splitToList("", 'a').size());
- assertEquals(0, StringUtils.splitToList(null, 'a').size());
+ assertEquals(0, splitToList("", 'a').size());
+ assertEquals(0, splitToList(null, 'a').size());
}
+ /**
+ * Test {@link StringUtils#splitToSet(String, char, boolean)}
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testSplitToSet() {
+ String value = "1# 2#3 #4#3";
+ Set values = splitToSet(value, '#', false);
+ assertEquals(ofSet("1", " 2", "3 ", "4", "3"), values);
+
+ values = splitToSet(value, '#', true);
+ assertEquals(ofSet("1", "2", "3", "4"), values);
+ }
+
+
@Test
public void testTranslate() throws Exception {
String s = "16314";
@@ -372,6 +393,10 @@ public void testParseParameters() {
assertEquals(0, illegalMap.size());
}
+ /**
+ * Test {@link StringUtils#toCommaDelimitedString(String, String...)}
+ * @since 2.7.8
+ */
@Test
public void testToCommaDelimitedString() {
String value = toCommaDelimitedString(null);
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java b/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java
index ac2b416975f..24c4f0028cf 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java
@@ -52,6 +52,11 @@ public void init() {
configManager.destroy();
}
+ @Test
+ public void testDestroy() {
+ assertTrue(configManager.configsCache.isEmpty());
+ }
+
@Test
public void testDefaultValues() {
// assert single
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
index 101a6d2e63e..570f7a49f0e 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ReferenceConfig.java
@@ -58,8 +58,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
-import static java.util.Collections.unmodifiableList;
import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR;
@@ -75,7 +75,7 @@
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost;
-import static org.apache.dubbo.common.utils.StringUtils.splitToList;
+import static org.apache.dubbo.common.utils.StringUtils.splitToSet;
import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY;
import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL;
@@ -178,8 +178,8 @@ public String getServices() {
* @since 2.7.8
*/
@Parameter(excluded = true)
- public List getSubscribedServices() {
- return unmodifiableList(splitToList(getServices(), COMMA_SEPARATOR_CHAR));
+ public Set getSubscribedServices() {
+ return splitToSet(getServices(), COMMA_SEPARATOR_CHAR);
}
/**
@@ -365,11 +365,14 @@ private T createProxy(Map map) {
}
if (registryURL != null) { // registry url is available
// for multi-subscription scenario, use 'zone-aware' policy by default
- URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
- // The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
- invoker = CLUSTER.join(new StaticDirectory(u, invokers));
+ String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
+ // The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
+ invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
} else { // not a registry url, must be direct invoke.
- invoker = CLUSTER.join(new StaticDirectory(invokers));
+ String cluster = CollectionUtils.isNotEmpty(invokers)
+ ? (invokers.get(0).getUrl() != null ? invokers.get(0).getUrl().getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME) : Cluster.DEFAULT)
+ : Cluster.DEFAULT;
+ invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
}
}
}
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java
index b7df8a43ace..cfea5806b90 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilder.java
@@ -128,15 +128,15 @@ public ServiceBuilder generic(String generic) {
return getThis();
}
- @Override
- public ServiceBuilder mock(String mock) {
- throw new IllegalArgumentException("mock doesn't support on provider side");
- }
-
- @Override
- public ServiceBuilder mock(Boolean mock) {
- throw new IllegalArgumentException("mock doesn't support on provider side");
- }
+// @Override
+// public ServiceBuilder mock(String mock) {
+// throw new IllegalArgumentException("mock doesn't support on provider side");
+// }
+
+// @Override
+// public ServiceBuilder mock(Boolean mock) {
+// throw new IllegalArgumentException("mock doesn't support on provider side");
+// }
public ServiceConfig build() {
ServiceConfig serviceConfig = new ServiceConfig<>();
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
index ba8b9a77f80..2e46fa26024 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/utils/ConfigValidationUtils.java
@@ -97,7 +97,6 @@
import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY;
import static org.apache.dubbo.config.Constants.ENVIRONMENT;
import static org.apache.dubbo.config.Constants.LAYER_KEY;
-import static org.apache.dubbo.config.Constants.LISTENER_KEY;
import static org.apache.dubbo.config.Constants.NAME;
import static org.apache.dubbo.config.Constants.ORGANIZATION;
import static org.apache.dubbo.config.Constants.OWNER;
@@ -297,7 +296,6 @@ public static void validateAbstractInterfaceConfig(AbstractInterfaceConfig confi
checkExtension(ProxyFactory.class, PROXY_KEY, config.getProxy());
checkExtension(Cluster.class, CLUSTER_KEY, config.getCluster());
checkMultiExtension(Filter.class, FILE_KEY, config.getFilter());
- checkMultiExtension(InvokerListener.class, LISTENER_KEY, config.getListener());
checkNameHasSymbol(LAYER_KEY, config.getLayer());
List methods = config.getMethods();
diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
index d08413743c3..1b843b6bbc7 100644
--- a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
+++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter
@@ -1,3 +1,3 @@
# since 2.7.8
-default = org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter
+local = org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter
remote = org.apache.dubbo.config.metadata.RemoteMetadataServiceExporter
\ No newline at end of file
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
index c1f42e2b7f9..4d157ff1100 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java
@@ -239,21 +239,21 @@ public void testGeneric2() throws Exception {
});
}
- @Test
- public void testMock() throws Exception {
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- ServiceConfig service = new ServiceConfig();
- service.setMock("true");
- });
- }
-
- @Test
- public void testMock2() throws Exception {
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- ServiceConfig service = new ServiceConfig();
- service.setMock(true);
- });
- }
+// @Test
+// public void testMock() throws Exception {
+// Assertions.assertThrows(IllegalArgumentException.class, () -> {
+// ServiceConfig service = new ServiceConfig();
+// service.setMock("true");
+// });
+// }
+//
+// @Test
+// public void testMock2() throws Exception {
+// Assertions.assertThrows(IllegalArgumentException.class, () -> {
+// ServiceConfig service = new ServiceConfig();
+// service.setMock(true);
+// });
+// }
@Test
public void testApplicationInUrl() {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java
index 210d92e251a..4d11ef43149 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ReferenceBuilderTest.java
@@ -26,6 +26,8 @@
import java.util.Collections;
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
+
class ReferenceBuilderTest {
@Test
@@ -95,8 +97,15 @@ void build() {
MethodConfig method = new MethodConfig();
ReferenceBuilder builder = new ReferenceBuilder<>();
- builder.id("id").interfaceClass(DemoService.class).protocol("protocol").client("client").url("url")
- .consumer(consumer).addMethod(method);
+ builder.id("id")
+ .interfaceClass(DemoService.class)
+ .protocol("protocol")
+ .client("client")
+ .url("url")
+ .consumer(consumer)
+ .addMethod(method)
+ // introduced since 2.7.8
+ .services("test-service", "test-service2");
ReferenceConfig config = builder.build();
ReferenceConfig config2 = builder.build();
@@ -107,6 +116,8 @@ void build() {
Assertions.assertEquals("client", config.getClient());
Assertions.assertEquals("url", config.getUrl());
Assertions.assertEquals(consumer, config.getConsumer());
+ Assertions.assertEquals("test-service,test-service2", config.getServices());
+ Assertions.assertEquals(ofSet("test-service", "test-service2"), config.getSubscribedServices());
Assertions.assertTrue(config.getMethods().contains(method));
Assertions.assertEquals(1, config.getMethods().size());
Assertions.assertNotSame(config, config2);
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java
index 24c4a4d02dc..b125547420e 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/RegistryBuilderTest.java
@@ -220,7 +220,7 @@ void build() {
.transporter("transporter").server("server").client("client").cluster("cluster").group("group")
.version("version").timeout(1000).session(2000).file("file").wait(Integer.valueOf(10)).isCheck(true)
.isDynamic(false).register(true).subscribe(false).isDefault(true).simplified(false).extraKeys("A")
- .appendParameter("default.num", "one").id("id").prefix("prefix");
+ .parameter("default.num", "one").id("id").prefix("prefix");
RegistryConfig config = builder.build();
RegistryConfig config2 = builder.build();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java
index 5b07c296fbf..ae07c2405fc 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ServiceBuilderTest.java
@@ -91,22 +91,22 @@ public void generic1() throws Exception {
builder.generic("illegal").build();
});
}
-
- @Test
- public void Mock() throws Exception {
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- ServiceBuilder builder = new ServiceBuilder();
- builder.mock("true");
- });
- }
-
- @Test
- public void Mock1() throws Exception {
- Assertions.assertThrows(IllegalArgumentException.class, () -> {
- ServiceBuilder builder = new ServiceBuilder();
- builder.mock(true);
- });
- }
+//
+// @Test
+// public void Mock() throws Exception {
+// Assertions.assertThrows(IllegalArgumentException.class, () -> {
+// ServiceBuilder builder = new ServiceBuilder();
+// builder.mock("true");
+// });
+// }
+//
+// @Test
+// public void Mock1() throws Exception {
+// Assertions.assertThrows(IllegalArgumentException.class, () -> {
+// ServiceBuilder builder = new ServiceBuilder();
+// builder.mock(true);
+// });
+// }
@Test
void build() {
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
new file mode 100644
index 00000000000..67a562d6687
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.config.event.listener;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.apache.dubbo.config.ServiceConfig;
+import org.apache.dubbo.config.bootstrap.EchoService;
+import org.apache.dubbo.config.bootstrap.EchoServiceImpl;
+import org.apache.dubbo.config.context.ConfigManager;
+import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import com.google.gson.Gson;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
+import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
+import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
+import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link PublishingServiceDefinitionListener} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class PublishingServiceDefinitionListenerTest {
+
+ private WritableMetadataService writableMetadataService;
+
+ @BeforeEach
+ public void init() {
+ String metadataType = DEFAULT_METADATA_STORAGE_TYPE;
+ ConfigManager configManager = ApplicationModel.getConfigManager();
+ ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-demo-provider");
+ applicationConfig.setMetadataType(metadataType);
+ configManager.setApplication(applicationConfig);
+ this.writableMetadataService = WritableMetadataService.getExtension(metadataType);
+ }
+
+ @AfterEach
+ public void reset() {
+ ApplicationModel.reset();
+ }
+
+ /**
+ * Test {@link ServiceConfigExportedEvent} arising
+ */
+ @Test
+ public void testOnServiceConfigExportedEvent() {
+ ServiceConfig serviceConfig = new ServiceConfig<>();
+ serviceConfig.setInterface(EchoService.class);
+ serviceConfig.setRef(new EchoServiceImpl());
+ serviceConfig.setRegistry(new RegistryConfig("N/A"));
+ serviceConfig.export();
+
+ String serviceDefinition = writableMetadataService.getServiceDefinition(EchoService.class.getName());
+
+ List exportedUrls = serviceConfig.getExportedUrls();
+
+ FullServiceDefinition fullServiceDefinition = buildFullDefinition(
+ serviceConfig.getInterfaceClass(),
+ exportedUrls.get(0)
+ .removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY)
+ .getParameters()
+ );
+
+ assertEquals(serviceDefinition, new Gson().toJson(fullServiceDefinition));
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporterTest.java
new file mode 100644
index 00000000000..e4d13a15289
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/RemoteMetadataServiceExporterTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.config.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.metadata.MetadataServiceExporter;
+import org.apache.dubbo.metadata.WritableMetadataService;
+import org.apache.dubbo.metadata.report.MetadataReportInstance;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.EchoService;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static java.util.Arrays.asList;
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.metadata.MetadataServiceExporter.getExtension;
+import static org.apache.dubbo.metadata.report.support.Constants.SYNC_REPORT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link RemoteMetadataServiceExporter} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class RemoteMetadataServiceExporterTest {
+
+ private static final URL METADATA_REPORT_URL = URL.valueOf("file://")
+ .addParameter(APPLICATION_KEY, "test")
+ .addParameter(SYNC_REPORT_KEY, "true");
+
+ private static final Class INTERFACE_CLASS = EchoService.class;
+
+ private static final String INTERFACE_NAME = INTERFACE_CLASS.getName();
+
+ private static final String APP_NAME = "test-service";
+
+ private static final URL BASE_URL = URL
+ .valueOf("dubbo://127.0.0.1:20880")
+ .setPath(INTERFACE_NAME)
+ .addParameter(APPLICATION_KEY, APP_NAME)
+ .addParameter(SIDE_KEY, "provider");
+
+ private final MetadataServiceExporter exporter = getExtension(REMOTE_METADATA_STORAGE_TYPE);
+
+ private WritableMetadataService writableMetadataService;
+
+ @BeforeEach
+ public void init() {
+ ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(APP_NAME));
+ MetadataReportInstance.init(METADATA_REPORT_URL);
+ writableMetadataService = WritableMetadataService.getDefaultExtension();
+ writableMetadataService.exportURL(BASE_URL);
+ }
+
+ @AfterEach
+ public void reset() {
+ ApplicationModel.reset();
+ }
+
+ @Test
+ public void testType() {
+ assertEquals(RemoteMetadataServiceExporter.class, exporter.getClass());
+ }
+
+ @Test
+ public void testSupports() {
+ assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
+ assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));
+ assertFalse(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
+ }
+
+ @Test
+ public void testExportAndUnexport() {
+ assertFalse(exporter.isExported());
+ assertEquals(exporter, exporter.export());
+ assertTrue(exporter.isExported());
+
+ assertEquals(asList(BASE_URL), exporter.getExportedURLs());
+
+ assertEquals(exporter, exporter.unexport());
+ assertFalse(exporter.isExported());
+ }
+}
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
new file mode 100644
index 00000000000..8096276f19a
--- /dev/null
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.metadata;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link MetadataServiceExporter} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class MetadataServiceExporterTest {
+
+ @Test
+ public void test() {
+ MetadataServiceExporter exporter = MetadataServiceExporter.getExtension(null);
+ assertEquals(exporter, MetadataServiceExporter.getDefaultExtension());
+ assertTrue(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
+ assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
+ assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));
+ }
+}
diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java
index a35645df40f..c7b6e77c285 100644
--- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java
+++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/extension/SpringExtensionFactory.java
@@ -72,7 +72,7 @@ public T getExtension(Class type, String name) {
}
}
- logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
+ //logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
return null;
}
diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java
index a86e5a98d6f..60837e274e8 100644
--- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java
+++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/beans/factory/annotation/ReferenceBeanBuilderTest.java
@@ -17,11 +17,11 @@
package org.apache.dubbo.config.spring.beans.factory.annotation;
+import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.rpc.model.ApplicationModel;
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -29,6 +29,8 @@
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -36,6 +38,7 @@
import java.util.HashMap;
import java.util.Map;
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.util.ReflectionUtils.findField;
@@ -43,6 +46,7 @@
* {@link ReferenceBeanBuilder} Test
*
* @see ReferenceBeanBuilder
+ * @see DubboReference
* @see Reference
* @since 2.6.4
*/
@@ -50,17 +54,7 @@
@ContextConfiguration(classes = ReferenceBeanBuilderTest.class)
public class ReferenceBeanBuilderTest {
- @Before
- public void setUp() {
- ApplicationModel.reset();
- }
-
- @After
- public void tearDown() {
- ApplicationModel.reset();
- }
-
- @Reference(
+ @DubboReference(
interfaceClass = CharSequence.class,
interfaceName = "java.lang.CharSequence",
version = "1.0.0", group = "TEST_GROUP", url = "dubbo://localhost:12345",
@@ -75,7 +69,11 @@ public void tearDown() {
timeout = 3, cache = "cache", filter = {"echo", "generic", "accesslog"},
listener = {"deprecated"}, parameters = {"n1=v1 ", "n2 = v2 ", " n3 = v3 "},
application = "application",
- module = "module", consumer = "consumer", monitor = "monitor", registry = {"registry"}
+ module = "module", consumer = "consumer", monitor = "monitor", registry = {"registry"},
+ // @since 2.7.3
+ id = "reference",
+ // @since 2.7.8
+ services = {"service1", "service2", "service3", "service2", "service1"}
)
private static final Object TEST_FIELD = new Object();
@@ -89,8 +87,9 @@ public void init() {
@Test
public void testBuild() throws Exception {
- Reference reference = findAnnotation(findField(getClass(), "TEST_FIELD"), Reference.class);
- ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder.create(reference, context.getClassLoader(), context);
+ DubboReference reference = findAnnotation(findField(getClass(), "TEST_FIELD"), DubboReference.class);
+ AnnotationAttributes attributes = AnnotationUtils.getAnnotationAttributes(reference, false, false);
+ ReferenceBeanBuilder beanBuilder = ReferenceBeanBuilder.create(attributes, context);
beanBuilder.interfaceClass(CharSequence.class);
ReferenceBean referenceBean = beanBuilder.build();
Assert.assertEquals(CharSequence.class, referenceBean.getInterfaceClass());
@@ -99,7 +98,7 @@ public void testBuild() throws Exception {
Assert.assertEquals("dubbo://localhost:12345", referenceBean.getUrl());
Assert.assertEquals("client", referenceBean.getClient());
Assert.assertEquals(true, referenceBean.isGeneric());
- Assert.assertNull(referenceBean.isInjvm());
+ Assert.assertTrue(referenceBean.isInjvm());
Assert.assertEquals(false, referenceBean.isCheck());
Assert.assertFalse(referenceBean.isInit());
Assert.assertEquals(true, referenceBean.getLazy());
@@ -126,6 +125,8 @@ public void testBuild() throws Exception {
Assert.assertEquals("cache", referenceBean.getCache());
Assert.assertEquals("echo,generic,accesslog", referenceBean.getFilter());
Assert.assertEquals("deprecated", referenceBean.getListener());
+ Assert.assertEquals("reference", referenceBean.getId());
+ Assert.assertEquals(ofSet("service1", "service2", "service3"), referenceBean.getSubscribedServices());
// parameters
Map parameters = new HashMap();
diff --git a/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java b/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java
index 8bfe7b2d2f2..e3978c6c136 100644
--- a/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java
+++ b/dubbo-filter/dubbo-filter-validation/src/main/java/org/apache/dubbo/validation/support/jvalidation/JValidator.java
@@ -186,7 +186,7 @@ private static Class> generateMethodParameterClass(Class> clazz, Method meth
private static String generateMethodParameterClassName(Class> clazz, Method method) {
StringBuilder builder = new StringBuilder().append(clazz.getName())
.append("_")
- .append(toUpperMethoName(method.getName()))
+ .append(toUpperMethodName(method.getName()))
.append("Parameter");
Class>[] parameterTypes = method.getParameterTypes();
@@ -211,7 +211,7 @@ private static boolean hasConstraintParameter(Method method) {
return false;
}
- private static String toUpperMethoName(String methodName) {
+ private static String toUpperMethodName(String methodName) {
return methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
}
@@ -292,7 +292,7 @@ public void validate(String methodName, Class>[] parameterTypes, Object[] argu
private Class methodClass(String methodName) {
Class> methodClass = null;
- String methodClassName = clazz.getName() + "$" + toUpperMethoName(methodName);
+ String methodClassName = clazz.getName() + "$" + toUpperMethodName(methodName);
Class cached = methodClassMap.get(methodClassName);
if (cached != null) {
return cached == clazz ? null : cached;
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java
index 00ab4cca31e..9ad130b2768 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/CompositeServiceNameMapping.java
@@ -45,17 +45,10 @@ private List getServiceNameMappings() {
if (this.serviceNameMappings == null) {
synchronized (this) {
if (this.serviceNameMappings == null) {
- Set serviceNameMappings = getExtensionLoader(ServiceNameMapping.class)
- .getSupportedExtensionInstances();
+ Set serviceNameMappings = loadAllServiceNameMappings();
- Iterator iterator = serviceNameMappings.iterator();
+ removeSelf(serviceNameMappings);
- while (iterator.hasNext()) {
- ServiceNameMapping serviceNameMapping = iterator.next();
- if (this.getClass().equals(serviceNameMapping.getClass())) {
- iterator.remove(); // Exclude self
- }
- }
this.serviceNameMappings = new LinkedList<>(serviceNameMappings);
}
}
@@ -63,6 +56,20 @@ private List getServiceNameMappings() {
return this.serviceNameMappings;
}
+ private Set loadAllServiceNameMappings() {
+ return getExtensionLoader(ServiceNameMapping.class).getSupportedExtensionInstances();
+ }
+
+ private void removeSelf(Set serviceNameMappings) {
+ Iterator iterator = serviceNameMappings.iterator();
+ while (iterator.hasNext()) {
+ ServiceNameMapping serviceNameMapping = iterator.next();
+ if (this.getClass().equals(serviceNameMapping.getClass())) {
+ iterator.remove(); // Remove self
+ }
+ }
+ }
+
@Override
public void map(URL exportedURL) {
List serviceNameMappings = getServiceNameMappings();
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
index d1275e749d6..fe99d2e32a4 100755
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilder.java
@@ -28,6 +28,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import static org.apache.dubbo.common.utils.ClassUtils.isSimpleType;
@@ -36,15 +37,12 @@
*/
public class TypeDefinitionBuilder {
private static final Logger logger = LoggerFactory.getLogger(TypeDefinitionBuilder.class);
- private static final List BUILDERS;
+ static final List BUILDERS;
static {
- List builders = new ArrayList<>();
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(TypeBuilder.class);
- for (String extensionName : extensionLoader.getSupportedExtensions()) {
- builders.add(extensionLoader.getExtension(extensionName));
- }
- BUILDERS = builders;
+ Set tbs = extensionLoader.getSupportedExtensionInstances();
+ BUILDERS = new ArrayList<>(tbs);
}
public static TypeDefinition build(Type type, Class> clazz, Map, TypeDefinition> typeCache) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
index d7022bd9555..57673fe5983 100755
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/definition/builder/TypeBuilder.java
@@ -17,6 +17,7 @@
package org.apache.dubbo.metadata.definition.builder;
import org.apache.dubbo.common.extension.SPI;
+import org.apache.dubbo.common.lang.Prioritized;
import org.apache.dubbo.metadata.definition.model.TypeDefinition;
import java.lang.reflect.Type;
@@ -26,7 +27,7 @@
* 2015/1/27.
*/
@SPI
-public interface TypeBuilder {
+public interface TypeBuilder extends Prioritized {
/**
* Whether the build accept the type or class passed in.
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
index cc254b129a7..068d3a040ed 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/MetadataReport.java
@@ -27,9 +27,9 @@
import com.google.gson.Gson;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -38,9 +38,11 @@
import static org.apache.dubbo.rpc.model.ApplicationModel.getName;
/**
+ * The interface to report the metadata
*
+ * @see AutoCloseable since 2.7.8
*/
-public interface MetadataReport {
+public interface MetadataReport extends AutoCloseable {
void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition);
@@ -69,9 +71,9 @@ default List getExportedURLs(ServiceMetadataIdentifier metadataIdentifie
return emptyList();
}
- void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set urls);
+ void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Collection urls);
- List getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
+ Collection getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
String getServiceDefinition(MetadataIdentifier metadataIdentifier);
@@ -154,4 +156,5 @@ default SortedSet getExportedURLs(String serviceName, String exportedSer
default String getExportedURLsContent(String serviceName, String exportedServicesRevision) {
return null;
}
-}
+
+}
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java
index 9e6b76b3c2b..f70678b30d0 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/identifier/BaseApplicationMetadataIdentifier.java
@@ -17,6 +17,7 @@
package org.apache.dubbo.metadata.report.identifier;
import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR;
+import static org.apache.dubbo.common.utils.PathUtils.buildPath;
import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_PATH_TAG;
import static org.apache.dubbo.metadata.MetadataConstants.KEY_SEPARATOR;
@@ -36,9 +37,7 @@ String getUniqueKey(KeyTypeEnum keyType, String... params) {
}
String getIdentifierKey(String... params) {
-
- return application
- + joinParams(KEY_SEPARATOR, params);
+ return application + joinParams(KEY_SEPARATOR, params);
}
private String joinParams(String joinChar, String... params) {
@@ -58,9 +57,7 @@ private String getFilePathKey(String... params) {
}
private String getFilePathKey(String pathTag, String... params) {
- return pathTag
- + application
- + joinParams(PATH_SEPARATOR, params);
+ return buildPath(pathTag, application, joinParams(PATH_SEPARATOR, params));
}
}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
index 1d394017447..f3b90db5e36 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReport.java
@@ -44,6 +44,7 @@
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -52,7 +53,6 @@
import java.util.SortedSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadLocalRandom;
@@ -61,11 +61,15 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import static java.util.concurrent.Executors.newScheduledThreadPool;
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
import static org.apache.dubbo.common.constants.CommonConstants.FILE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.common.utils.StringUtils.replace;
import static org.apache.dubbo.metadata.report.support.Constants.CYCLE_REPORT_KEY;
import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_RETRY_PERIOD;
@@ -86,24 +90,52 @@ public abstract class AbstractMetadataReport implements MetadataReport {
// Log output
protected final Logger logger = LoggerFactory.getLogger(getClass());
- // Local disk cache, where the special key value.registries records the list of metadata centers, and the others are the list of notified service providers
- final Properties properties = new Properties();
- private final ExecutorService reportCacheExecutor = Executors.newFixedThreadPool(1, new NamedThreadFactory("DubboSaveMetadataReport", true));
+ private final AtomicBoolean initialized = new AtomicBoolean(false);
+
final Map allMetadataReports = new ConcurrentHashMap<>(4);
- private final AtomicLong lastCacheChanged = new AtomicLong();
final Map failedReports = new ConcurrentHashMap<>(4);
+
private URL reportURL;
boolean syncReport;
+
// Local disk cache file
- File file;
- private AtomicBoolean initialized = new AtomicBoolean(false);
- public MetadataReportRetry metadataReportRetry;
+ File localCacheFile;
+ // Local disk cache, where the special key value.registries records the list of metadata centers, and the others are the list of notified service providers
+ final Properties properties = new Properties();
+
+ private final AtomicLong lastCacheChanged = new AtomicLong();
+
+ // ThreadPoolExecutors
+ private final ExecutorService reportCacheExecutor;
+
+ public final MetadataReportRetry metadataReportRetry;
+
+ private final ScheduledExecutorService cycleReportExecutor;
public AbstractMetadataReport(URL reportServerURL) {
setUrl(reportServerURL);
+
+ this.localCacheFile = initializeLocalCacheFile(reportServerURL);
+ loadProperties();
+ syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);
+ metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
+ reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
+ this.reportCacheExecutor = newSingleThreadExecutor(new NamedThreadFactory("DubboSaveMetadataReport", true));
+ this.cycleReportExecutor = newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
+ // cycle report the data switch
+ if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
+ cycleReportExecutor.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private File initializeLocalCacheFile(URL reportServerURL) {
// Start file save timer
- String defaultFilename = System.getProperty("user.home") + "/.dubbo/dubbo-metadata-" + reportServerURL.getParameter(APPLICATION_KEY) + "-" + reportServerURL.getAddress().replaceAll(":", "-") + ".cache";
+ String defaultFilename = System.getProperty("user.home") +
+ "/.dubbo/dubbo-metadata-" +
+ reportServerURL.getParameter(APPLICATION_KEY) + "-" +
+ replace(reportServerURL.getAddress(), ":", "-") +
+ ".cache";
String filename = reportServerURL.getParameter(FILE_KEY, defaultFilename);
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
@@ -118,16 +150,7 @@ public AbstractMetadataReport(URL reportServerURL) {
file.delete();
}
}
- this.file = file;
- loadProperties();
- syncReport = reportServerURL.getParameter(SYNC_REPORT_KEY, false);
- metadataReportRetry = new MetadataReportRetry(reportServerURL.getParameter(RETRY_TIMES_KEY, DEFAULT_METADATA_REPORT_RETRY_TIMES),
- reportServerURL.getParameter(RETRY_PERIOD_KEY, DEFAULT_METADATA_REPORT_RETRY_PERIOD));
- // cycle report the data switch
- if (reportServerURL.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
- ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboMetadataReportTimer", true));
- scheduler.scheduleAtFixedRate(this::publishAll, calculateStartTime(), ONE_DAY_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
- }
+ return file;
}
public URL getUrl() {
@@ -145,12 +168,12 @@ private void doSaveProperties(long version) {
if (version < lastCacheChanged.get()) {
return;
}
- if (file == null) {
+ if (localCacheFile == null) {
return;
}
// Save
try {
- File lockfile = new File(file.getAbsolutePath() + ".lock");
+ File lockfile = new File(localCacheFile.getAbsolutePath() + ".lock");
if (!lockfile.exists()) {
lockfile.createNewFile();
}
@@ -158,14 +181,14 @@ private void doSaveProperties(long version) {
FileChannel channel = raf.getChannel()) {
FileLock lock = channel.tryLock();
if (lock == null) {
- throw new IOException("Can not lock the metadataReport cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties");
+ throw new IOException("Can not lock the metadataReport cache file " + localCacheFile.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.metadata.file=xxx.properties");
}
// Save
try {
- if (!file.exists()) {
- file.createNewFile();
+ if (!localCacheFile.exists()) {
+ localCacheFile.createNewFile();
}
- try (FileOutputStream outputFile = new FileOutputStream(file)) {
+ try (FileOutputStream outputFile = new FileOutputStream(localCacheFile)) {
properties.store(outputFile, "Dubbo metadataReport Cache");
}
} finally {
@@ -183,20 +206,20 @@ private void doSaveProperties(long version) {
}
void loadProperties() {
- if (file != null && file.exists()) {
- try (InputStream in = new FileInputStream(file)) {
+ if (localCacheFile != null && localCacheFile.exists()) {
+ try (InputStream in = new FileInputStream(localCacheFile)) {
properties.load(in);
if (logger.isInfoEnabled()) {
- logger.info("Load service store file " + file + ", data: " + properties);
+ logger.info("Load service store file " + localCacheFile + ", data: " + properties);
}
} catch (Throwable e) {
- logger.warn("Failed to load service store file " + file, e);
+ logger.warn("Failed to load service store file " + localCacheFile, e);
}
}
}
private void saveProperties(MetadataIdentifier metadataIdentifier, String value, boolean add, boolean sync) {
- if (file == null) {
+ if (localCacheFile == null) {
return;
}
@@ -318,7 +341,7 @@ public List getExportedURLs(ServiceMetadataIdentifier metadataIdentifier
}
@Override
- public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Set urls) {
+ public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, Collection urls) {
if (syncReport) {
doSaveSubscriberData(subscriberMetadataIdentifier, new Gson().toJson(urls));
} else {
@@ -328,7 +351,7 @@ public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataId
@Override
- public List getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
+ public Set getSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
String content = doGetSubscribedURLs(subscriberMetadataIdentifier);
Type setType = new TypeToken>() {
}.getType();
@@ -392,7 +415,7 @@ long calculateStartTime() {
class MetadataReportRetry {
protected final Logger logger = LoggerFactory.getLogger(getClass());
- final ScheduledExecutorService retryExecutor = Executors.newScheduledThreadPool(0, new NamedThreadFactory("DubboMetadataReportRetryTimer", true));
+ final ScheduledExecutorService retryExecutor = newScheduledThreadPool(0, new NamedThreadFactory("DubboMetadataReportRetryTimer", true));
volatile ScheduledFuture retryScheduledFuture;
final AtomicInteger retryCounter = new AtomicInteger(0);
// retry task schedule period
@@ -435,8 +458,10 @@ public void run() {
}
void cancelRetryTask() {
- retryScheduledFuture.cancel(false);
- retryExecutor.shutdown();
+ if (retryScheduledFuture != null) {
+ retryScheduledFuture.cancel(false);
+ }
+ shutdown(retryExecutor);
}
}
@@ -451,6 +476,13 @@ private void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadat
doSaveSubscriberData(subscriberMetadataIdentifier, encodedUrlList);
}
+ @Override
+ public final void close() throws Exception {
+ this.shutdownThreadPoolExecutors();
+ this.clearCache();
+ doClose();
+ }
+
protected abstract void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions);
protected abstract void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String serviceParameterString);
@@ -465,4 +497,35 @@ private void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadat
protected abstract String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier);
+ /**
+ * Close other resources
+ *
+ * @since 2.7.8
+ */
+ protected void doClose() throws Exception {
+
+ }
+
+ private void clearCache() {
+ this.properties.clear();
+ this.allMetadataReports.clear();
+ this.failedReports.clear();
+ this.localCacheFile.delete();
+ }
+
+ private void shutdownThreadPoolExecutors() {
+ this.metadataReportRetry.cancelRetryTask();
+ shutdown(this.reportCacheExecutor);
+ shutdown(cycleReportExecutor);
+ }
+
+ private static void shutdown(ExecutorService executorService) {
+ if (executorService == null) {
+ return;
+ }
+ if (!executorService.isShutdown()) {
+ executorService.shutdown();
+ }
+ }
+
}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
index 6111fb1aa3e..d962ca51872 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReport.java
@@ -123,6 +123,11 @@ protected void saveMetadata(MetadataIdentifier metadataIdentifier, String value)
dynamicConfiguration.publishConfig(key, group, value);
}
+ protected String getMetadata(ServiceMetadataIdentifier metadataIdentifier) {
+ String key = getKey(metadataIdentifier);
+ return dynamicConfiguration.getConfig(key, group);
+ }
+
protected String getMetadata(MetadataIdentifier metadataIdentifier) {
String key = getKey(metadataIdentifier);
return dynamicConfiguration.getConfig(key, group);
@@ -150,4 +155,8 @@ protected String getKey(BaseMetadataIdentifier metadataIdentifier) {
protected String getKey(MetadataIdentifier metadataIdentifier) {
return metadataIdentifier.getUniqueKey(keyType);
}
+
+ protected void doClose() throws Exception {
+ this.dynamicConfiguration.close();
+ }
}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
index 4ca3a7e1719..5b0f780ff4e 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportFactory.java
@@ -47,7 +47,7 @@ public abstract class ConfigCenterBasedMetadataReportFactory implements Metadata
private static final String URL_PATH = MetadataReport.class.getName();
// Registry Collection Map
- private static final Map metadataReportCache = new ConcurrentHashMap();
+ private static final Map metadataReportCache = new ConcurrentHashMap();
private final KeyTypeEnum keyType;
@@ -59,7 +59,7 @@ public ConfigCenterBasedMetadataReportFactory(KeyTypeEnum keyType) {
}
@Override
- public MetadataReport getMetadataReport(URL url) {
+ public ConfigCenterBasedMetadataReport getMetadataReport(URL url) {
url = url.setPath(URL_PATH).removeParameters(EXPORT_KEY, REFER_KEY);
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/file/FileSystemMetadataReportFactory.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/file/FileSystemMetadataReportFactory.java
new file mode 100644
index 00000000000..c4cc4874b85
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/report/support/file/FileSystemMetadataReportFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.metadata.report.support.file;
+
+import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
+import org.apache.dubbo.metadata.report.support.ConfigCenterBasedMetadataReportFactory;
+
+/**
+ * The implementation of {@link ConfigCenterBasedMetadataReportFactory} based on File System
+ *
+ * @see ConfigCenterBasedMetadataReportFactory
+ * @since 2.7.8
+ */
+public class FileSystemMetadataReportFactory extends ConfigCenterBasedMetadataReportFactory {
+
+ public FileSystemMetadataReportFactory() {
+ super(KeyTypeEnum.PATH);
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
new file mode 100644
index 00000000000..b686fced440
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.report.MetadataReportFactory
@@ -0,0 +1 @@
+file = org.apache.dubbo.metadata.report.support.file.FileSystemMetadataReportFactory
\ No newline at end of file
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/CompositeServiceNameMappingTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/CompositeServiceNameMappingTest.java
new file mode 100644
index 00000000000..48243f8c49e
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/CompositeServiceNameMappingTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.metadata;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.file.FileSystemDynamicConfiguration;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.EchoService;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static java.util.Collections.singleton;
+import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
+import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
+import static org.apache.dubbo.common.utils.CollectionUtils.ofSet;
+import static org.apache.dubbo.metadata.DynamicConfigurationServiceNameMapping.buildGroup;
+import static org.apache.dubbo.rpc.model.ApplicationModel.getApplicationConfig;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * {@link CompositeServiceNameMapping} Test
+ *
+ * @since 2.7.8
+ */
+public class CompositeServiceNameMappingTest {
+
+ private static final URL BASE_URL = URL.valueOf("dubbo://127.0.0.1:20880")
+ .setPath(EchoService.class.getName())
+ .addParameter(GROUP_KEY, "default")
+ .addParameter(VERSION_KEY, "1.0.0");
+
+ private static final String APP_NAME = "test-service";
+
+ private ServiceNameMapping serviceNameMapping;
+
+ private FileSystemDynamicConfiguration dynamicConfiguration;
+
+ @BeforeEach
+ public void init() {
+ serviceNameMapping = ServiceNameMapping.getDefaultExtension();
+ dynamicConfiguration = new FileSystemDynamicConfiguration();
+ ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(APP_NAME));
+ ApplicationModel.getEnvironment().setDynamicConfiguration(dynamicConfiguration);
+ }
+
+ @AfterEach
+ public void reset() {
+ FileUtils.deleteQuietly(dynamicConfiguration.getRootDirectory());
+ ApplicationModel.reset();
+ }
+
+ @Test
+ public void testType() {
+ assertEquals(CompositeServiceNameMapping.class, serviceNameMapping.getClass());
+ }
+
+ @Test
+ public void testMap() {
+ serviceNameMapping.map(BASE_URL);
+ assertNotNull(dynamicConfiguration.getConfig(APP_NAME,
+ buildGroup(BASE_URL.getServiceInterface(), null, null, null)));
+ }
+
+ @Test
+ public void testGet() {
+ serviceNameMapping.map(BASE_URL);
+ Set serviceNames = serviceNameMapping.get(BASE_URL);
+ assertEquals(singleton(APP_NAME), serviceNames);
+
+ getApplicationConfig().setName("service1");
+ serviceNameMapping.map(BASE_URL);
+ serviceNames = serviceNameMapping.get(BASE_URL);
+ assertEquals(ofSet(APP_NAME, "service1"), serviceNames);
+
+ serviceNames = serviceNameMapping.get(BASE_URL
+ .setPath("com.acme.Interface1")
+ .removeParameter(VERSION_KEY)
+ );
+ assertEquals(singleton("Service1"), serviceNames);
+
+ serviceNames = serviceNameMapping.get(BASE_URL.addParameter(SUBSCRIBED_SERVICE_NAMES_KEY, "s1 , s2 , s3 "));
+ assertEquals(ofSet("s1", "s2", "s3"), serviceNames);
+ }
+}
+
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataConstantsTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataConstantsTest.java
new file mode 100644
index 00000000000..d0c201b0d7c
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataConstantsTest.java
@@ -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.
+ */
+package org.apache.dubbo.metadata;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MetadataConstants} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class MetadataConstantsTest {
+
+ @Test
+ public void testConstants() {
+ assertEquals("exported-urls", MetadataConstants.EXPORTED_URLS_TAG);
+ assertEquals("subscribed-urls", MetadataConstants.SUBSCRIBED_URLS_TAG);
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceTypeTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceTypeTest.java
new file mode 100644
index 00000000000..11eec1dff4b
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceTypeTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metadata;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.dubbo.metadata.MetadataServiceType.COMPOSITE;
+import static org.apache.dubbo.metadata.MetadataServiceType.DEFAULT;
+import static org.apache.dubbo.metadata.MetadataServiceType.REMOTE;
+import static org.apache.dubbo.metadata.MetadataServiceType.getOrDefault;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link MetadataServiceType} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class MetadataServiceTypeTest {
+
+ @Test
+ public void testGetValue() {
+ assertEquals("local", DEFAULT.getValue());
+ assertEquals("remote", REMOTE.getValue());
+ assertEquals("composite", COMPOSITE.getValue());
+ }
+
+ @Test
+ public void testGetOrDefault() {
+ assertEquals(DEFAULT, getOrDefault("local"));
+ assertEquals(REMOTE, getOrDefault("remote"));
+ assertEquals(COMPOSITE, getOrDefault("composite"));
+ assertEquals(DEFAULT, getOrDefault("others"));
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
index 487078eecdc..b36ebcb480a 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/URLRevisionResolverTest.java
@@ -34,7 +34,7 @@ public class URLRevisionResolverTest {
private static final String URL = "dubbo://192.168.0.102:20881/org.apache.dubbo.metadata.URLRevisionResolverTest";
- private final URLRevisionResolver resolver = new URLRevisionResolver();
+ private final URLRevisionResolver resolver = URLRevisionResolver.INSTANCE;
@Test
public void testResolve() {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.java
new file mode 100644
index 00000000000..075a69e8140
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/Test3TypeBuilder.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.metadata.definition;
+
+import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
+import org.apache.dubbo.metadata.definition.model.TypeDefinition;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * test for sort
+ */
+public class Test3TypeBuilder implements TypeBuilder {
+ // it is smaller than the implements of TypeBuilder
+ public int getPriority(){
+ return 10;
+ }
+
+ @Override
+ public boolean accept(Type type, Class> clazz) {
+ return false;
+ }
+
+ @Override
+ public TypeDefinition build(Type type, Class> clazz, Map, TypeDefinition> typeCache) {
+ return null;
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.java
new file mode 100644
index 00000000000..bf7e9c6076c
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TestTypeBuilder.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.metadata.definition;
+
+import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
+import org.apache.dubbo.metadata.definition.model.TypeDefinition;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * test for sort
+ */
+public class TestTypeBuilder implements TypeBuilder {
+ // it is smaller than the implements of TypeBuilder
+ public int getPriority(){
+ return -3;
+ }
+
+ @Override
+ public boolean accept(Type type, Class> clazz) {
+ return false;
+ }
+
+ @Override
+ public TypeDefinition build(Type type, Class> clazz, Map, TypeDefinition> typeCache) {
+ return null;
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
new file mode 100644
index 00000000000..11d03a15ad1
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/definition/TypeDefinitionBuilderTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.metadata.definition;
+
+import org.apache.dubbo.metadata.definition.builder.TypeBuilder;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TypeDefinitionBuilderTest {
+
+ @Test
+ public void testSortTypeBuilder(){
+ TypeBuilder tb = TypeDefinitionBuilder.BUILDERS.get(0);
+ Assertions.assertTrue(tb instanceof TestTypeBuilder);
+
+ tb = TypeDefinitionBuilder.BUILDERS.get(TypeDefinitionBuilder.BUILDERS.size()-1);
+ Assertions.assertTrue(tb instanceof Test3TypeBuilder);
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnumTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnumTest.java
new file mode 100644
index 00000000000..c4dae1f9190
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/identifier/KeyTypeEnumTest.java
@@ -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.
+ */
+package org.apache.dubbo.metadata.report.identifier;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link KeyTypeEnum} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class KeyTypeEnumTest {
+
+ /**
+ * {@link KeyTypeEnum#build(String, String...)}
+ */
+ @Test
+ public void testBuild() {
+ assertEquals("/A/B/C", KeyTypeEnum.PATH.build("/A", "/B", "C"));
+ assertEquals("A:B:C", KeyTypeEnum.UNIQUE_KEY.build("A", "B", "C"));
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
index 30d4ad1a300..3aa5c029d20 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportFactoryTest.java
@@ -28,9 +28,9 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -65,7 +65,7 @@ public List getExportedURLs(ServiceMetadataIdentifier metadataIdentifier
@Override
public void saveSubscribedData(SubscriberMetadataIdentifier subscriberMetadataIdentifier,
- Set urls) {
+ Collection urls) {
}
@@ -84,6 +84,11 @@ public void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier,
store.put(consumerMetadataIdentifier.getIdentifierKey(), JSON.toJSONString(serviceParameterMap));
}
+ @Override
+ public void close() throws Exception {
+
+ }
+
Map store = new ConcurrentHashMap<>();
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
index 9d4267f7e3d..c5325a1fa30 100644
--- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/AbstractMetadataReportTest.java
@@ -18,14 +18,18 @@
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.rpc.model.ApplicationModel;
import com.google.gson.Gson;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,10 +39,15 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.SortedSet;
import java.util.concurrent.ConcurrentHashMap;
+import static java.util.Collections.emptySet;
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
*
@@ -52,17 +61,25 @@ public class AbstractMetadataReportTest {
public void before() {
URL url = URL.valueOf("zookeeper://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic");
abstractMetadataReport = new NewMetadataReport(url);
+ // set the simple name of current class as the application name
+ ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(getClass().getSimpleName()));
+ }
+
+ @AfterEach
+ public void reset() {
+ // reset
+ ApplicationModel.reset();
}
@Test
public void testGetProtocol() {
URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic&side=provider");
String protocol = abstractMetadataReport.getProtocol(url);
- Assertions.assertEquals(protocol, "provider");
+ assertEquals(protocol, "provider");
URL url2 = URL.valueOf("consumer://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic");
String protocol2 = abstractMetadataReport.getProtocol(url2);
- Assertions.assertEquals(protocol2, "consumer");
+ assertEquals(protocol2, "consumer");
}
@Test
@@ -93,7 +110,7 @@ public void testFileExistAfterPut() throws InterruptedException, ClassNotFoundEx
URL singleUrl = URL.valueOf("redis://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.metadata.store.InterfaceNameTestService?version=1.0.0&application=singleTest");
NewMetadataReport singleMetadataReport = new NewMetadataReport(singleUrl);
- Assertions.assertFalse(singleMetadataReport.file.exists());
+ Assertions.assertFalse(singleMetadataReport.localCacheFile.exists());
String interfaceName = "org.apache.dubbo.metadata.store.InterfaceNameTestService";
String version = "1.0.0";
@@ -102,8 +119,8 @@ public void testFileExistAfterPut() throws InterruptedException, ClassNotFoundEx
MetadataIdentifier providerMetadataIdentifier = storePrivider(singleMetadataReport, interfaceName, version, group, application);
Thread.sleep(2000);
- Assertions.assertTrue(singleMetadataReport.file.exists());
- Assertions.assertTrue(singleMetadataReport.properties.containsKey(providerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY)));
+ assertTrue(singleMetadataReport.localCacheFile.exists());
+ assertTrue(singleMetadataReport.properties.containsKey(providerMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY)));
}
@Test
@@ -117,22 +134,22 @@ public void testRetry() throws InterruptedException, ClassNotFoundException {
retryReport.metadataReportRetry.retryPeriod = 400L;
URL url = URL.valueOf("dubbo://" + NetUtils.getLocalAddress().getHostName() + ":4444/org.apache.dubbo.TestService?version=1.0.0&application=vic");
Assertions.assertNull(retryReport.metadataReportRetry.retryScheduledFuture);
- Assertions.assertEquals(0, retryReport.metadataReportRetry.retryCounter.get());
- Assertions.assertTrue(retryReport.store.isEmpty());
- Assertions.assertTrue(retryReport.failedReports.isEmpty());
+ assertEquals(0, retryReport.metadataReportRetry.retryCounter.get());
+ assertTrue(retryReport.store.isEmpty());
+ assertTrue(retryReport.failedReports.isEmpty());
storePrivider(retryReport, interfaceName, version, group, application);
Thread.sleep(150);
- Assertions.assertTrue(retryReport.store.isEmpty());
+ assertTrue(retryReport.store.isEmpty());
Assertions.assertFalse(retryReport.failedReports.isEmpty());
Assertions.assertNotNull(retryReport.metadataReportRetry.retryScheduledFuture);
Thread.sleep(2000L);
- Assertions.assertTrue(retryReport.metadataReportRetry.retryCounter.get() != 0);
- Assertions.assertTrue(retryReport.metadataReportRetry.retryCounter.get() >= 3);
+ assertTrue(retryReport.metadataReportRetry.retryCounter.get() != 0);
+ assertTrue(retryReport.metadataReportRetry.retryCounter.get() >= 3);
Assertions.assertFalse(retryReport.store.isEmpty());
- Assertions.assertTrue(retryReport.failedReports.isEmpty());
+ assertTrue(retryReport.failedReports.isEmpty());
}
@Test
@@ -152,8 +169,8 @@ public void testRetryCancel() throws InterruptedException, ClassNotFoundExceptio
Assertions.assertFalse(retryReport.metadataReportRetry.retryScheduledFuture.isCancelled());
Assertions.assertFalse(retryReport.metadataReportRetry.retryExecutor.isShutdown());
Thread.sleep(1000L);
- Assertions.assertTrue(retryReport.metadataReportRetry.retryScheduledFuture.isCancelled());
- Assertions.assertTrue(retryReport.metadataReportRetry.retryExecutor.isShutdown());
+ assertTrue(retryReport.metadataReportRetry.retryScheduledFuture.isCancelled());
+ assertTrue(retryReport.metadataReportRetry.retryExecutor.isShutdown());
}
@@ -185,42 +202,42 @@ private MetadataIdentifier storeConsumer(AbstractMetadataReport abstractMetadata
@Test
public void testPublishAll() throws ClassNotFoundException, InterruptedException {
- Assertions.assertTrue(abstractMetadataReport.store.isEmpty());
- Assertions.assertTrue(abstractMetadataReport.allMetadataReports.isEmpty());
+ assertTrue(abstractMetadataReport.store.isEmpty());
+ assertTrue(abstractMetadataReport.allMetadataReports.isEmpty());
String interfaceName = "org.apache.dubbo.metadata.store.InterfaceNameTestService";
String version = "1.0.0";
String group = null;
String application = "vic";
MetadataIdentifier providerMetadataIdentifier1 = storePrivider(abstractMetadataReport, interfaceName, version, group, application);
Thread.sleep(1000);
- Assertions.assertEquals(abstractMetadataReport.allMetadataReports.size(), 1);
- Assertions.assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier1)).getParameters().containsKey("testPKey"));
+ assertEquals(abstractMetadataReport.allMetadataReports.size(), 1);
+ assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier1)).getParameters().containsKey("testPKey"));
MetadataIdentifier providerMetadataIdentifier2 = storePrivider(abstractMetadataReport, interfaceName, version + "_2", group + "_2", application);
Thread.sleep(1000);
- Assertions.assertEquals(abstractMetadataReport.allMetadataReports.size(), 2);
- Assertions.assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().containsKey("testPKey"));
- Assertions.assertEquals(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().get("version"), version + "_2");
+ assertEquals(abstractMetadataReport.allMetadataReports.size(), 2);
+ assertTrue(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().containsKey("testPKey"));
+ assertEquals(((FullServiceDefinition) abstractMetadataReport.allMetadataReports.get(providerMetadataIdentifier2)).getParameters().get("version"), version + "_2");
Map tmpMap = new HashMap<>();
tmpMap.put("testKey", "value");
MetadataIdentifier consumerMetadataIdentifier = storeConsumer(abstractMetadataReport, interfaceName, version + "_3", group + "_3", application, tmpMap);
Thread.sleep(1000);
- Assertions.assertEquals(abstractMetadataReport.allMetadataReports.size(), 3);
+ assertEquals(abstractMetadataReport.allMetadataReports.size(), 3);
Map tmpMapResult = (Map) abstractMetadataReport.allMetadataReports.get(consumerMetadataIdentifier);
- Assertions.assertEquals(tmpMapResult.get("testPKey"), "9090");
- Assertions.assertEquals(tmpMapResult.get("testKey"), "value");
- Assertions.assertEquals(3, abstractMetadataReport.store.size());
+ assertEquals(tmpMapResult.get("testPKey"), "9090");
+ assertEquals(tmpMapResult.get("testKey"), "value");
+ assertEquals(3, abstractMetadataReport.store.size());
abstractMetadataReport.store.clear();
- Assertions.assertEquals(0, abstractMetadataReport.store.size());
+ assertEquals(0, abstractMetadataReport.store.size());
abstractMetadataReport.publishAll();
Thread.sleep(200);
- Assertions.assertEquals(3, abstractMetadataReport.store.size());
+ assertEquals(3, abstractMetadataReport.store.size());
String v = abstractMetadataReport.store.get(providerMetadataIdentifier1.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
Gson gson = new Gson();
@@ -244,11 +261,53 @@ public void testCalculateStartTime() {
long t = abstractMetadataReport.calculateStartTime() + System.currentTimeMillis();
Calendar c = Calendar.getInstance();
c.setTimeInMillis(t);
- Assertions.assertTrue(c.get(Calendar.HOUR_OF_DAY) >= 2);
- Assertions.assertTrue(c.get(Calendar.HOUR_OF_DAY) <= 6);
+ assertTrue(c.get(Calendar.HOUR_OF_DAY) >= 2);
+ assertTrue(c.get(Calendar.HOUR_OF_DAY) <= 6);
}
}
+ /**
+ * Test {@link MetadataReport#saveExportedURLs(String, String, String)} method
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testSaveExportedURLs() {
+ String serviceName = null;
+ String exportedServiceRevision = null;
+ String exportedURLsContent = null;
+ SortedSet exportedURLs = null;
+ // Default methods return true
+ assertTrue(abstractMetadataReport.saveExportedURLs(exportedURLs));
+ assertTrue(abstractMetadataReport.saveExportedURLs(exportedServiceRevision, exportedURLs));
+ assertTrue(abstractMetadataReport.saveExportedURLs(serviceName, exportedServiceRevision, exportedURLs));
+ assertTrue(abstractMetadataReport.saveExportedURLs(serviceName, exportedServiceRevision, exportedURLsContent));
+ }
+
+ /**
+ * Test {@link MetadataReport#getExportedURLs(String, String)} method
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testGetExportedURLs() {
+ String serviceName = null;
+ String exportedServiceRevision = null;
+ assertEquals(emptySet(), abstractMetadataReport.getExportedURLs(serviceName, exportedServiceRevision));
+ }
+
+ /**
+ * Test {@link MetadataReport#getExportedURLsContent(String, String)} method
+ *
+ * @since 2.7.8
+ */
+ @Test
+ public void testGetExportedURLsContent() {
+ String serviceName = null;
+ String exportedServiceRevision = null;
+ assertNull(abstractMetadataReport.getExportedURLsContent(serviceName, exportedServiceRevision));
+ }
+
private FullServiceDefinition toServiceDefinition(String v) {
Gson gson = new Gson();
FullServiceDefinition data = gson.fromJson(v, FullServiceDefinition.class);
@@ -256,8 +315,8 @@ private FullServiceDefinition toServiceDefinition(String v) {
}
private void checkParam(Map map, String application, String version) {
- Assertions.assertEquals(map.get("application"), application);
- Assertions.assertEquals(map.get("version"), version);
+ assertEquals(map.get("application"), application);
+ assertEquals(map.get("version"), version);
}
private Map queryUrlToMap(String urlQuery) {
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportTest.java
new file mode 100644
index 00000000000..32205b9a1fd
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/report/support/ConfigCenterBasedMetadataReportTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.metadata.report.support;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.metadata.URLRevisionResolver;
+import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
+import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
+import org.apache.dubbo.metadata.report.MetadataReport;
+import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
+import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
+import org.apache.dubbo.metadata.report.support.file.FileSystemMetadataReportFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.service.EchoService;
+
+import com.google.gson.Gson;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY;
+import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
+import static org.apache.dubbo.metadata.report.support.Constants.SYNC_REPORT_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * {@link ConfigCenterBasedMetadataReport} Test-Cases
+ *
+ * @since 2.7.8
+ */
+public class ConfigCenterBasedMetadataReportTest {
+
+ private static final URL REPORT_SERVER_URL = URL.valueOf("file://")
+ .addParameter(APPLICATION_KEY, "test")
+ .addParameter(SYNC_REPORT_KEY, "true");
+
+ private static final Class INTERFACE_CLASS = EchoService.class;
+
+ private static final String INTERFACE_NAME = INTERFACE_CLASS.getName();
+
+ private static final String APP_NAME = "test-service";
+
+ private static final URL BASE_URL = URL
+ .valueOf("dubbo://127.0.0.1:20880")
+ .setPath(INTERFACE_NAME)
+ .addParameter(APPLICATION_KEY, APP_NAME)
+ .addParameter(SIDE_KEY, "provider");
+
+ private ConfigCenterBasedMetadataReport metadataReport;
+
+ @BeforeEach
+ public void init() {
+ ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("test-service"));
+ this.metadataReport = new FileSystemMetadataReportFactory().getMetadataReport(REPORT_SERVER_URL);
+ }
+
+ @AfterEach
+ public void reset() throws Exception {
+ ApplicationModel.reset();
+ this.metadataReport.close();
+ }
+
+ /**
+ * Test {@link MetadataReport#storeProviderMetadata(MetadataIdentifier, ServiceDefinition)} and
+ * {@link MetadataReport#getServiceDefinition(MetadataIdentifier)}
+ */
+ @Test
+ public void testStoreProviderMetadataAndGetServiceDefinition() {
+ MetadataIdentifier metadataIdentifier = new MetadataIdentifier(BASE_URL);
+ ServiceDefinition serviceDefinition = ServiceDefinitionBuilder.buildFullDefinition(INTERFACE_CLASS, BASE_URL.getParameters());
+ metadataReport.storeProviderMetadata(metadataIdentifier, serviceDefinition);
+ String serviceDefinitionJSON = metadataReport.getServiceDefinition(metadataIdentifier);
+ assertEquals(serviceDefinitionJSON, new Gson().toJson(serviceDefinition));
+ }
+
+ /**
+ * Test {@link MetadataReport#storeConsumerMetadata(MetadataIdentifier, Map)} and
+ * {@link MetadataReport#getServiceDefinition(MetadataIdentifier)}
+ */
+ @Test
+ public void testStoreConsumerMetadata() {
+ MetadataIdentifier metadataIdentifier = new MetadataIdentifier(BASE_URL);
+ metadataReport.storeConsumerMetadata(metadataIdentifier, BASE_URL.getParameters());
+ String parametersJSON = metadataReport.getServiceDefinition(metadataIdentifier);
+ assertEquals(parametersJSON, new Gson().toJson(BASE_URL.getParameters()));
+ }
+
+ /**
+ * Test {@link MetadataReport#saveServiceMetadata(ServiceMetadataIdentifier, URL)} and
+ * {@link MetadataReport#removeServiceMetadata(ServiceMetadataIdentifier)}
+ */
+ @Test
+ public void testSaveServiceMetadataAndRemoveServiceMetadata() {
+ ServiceMetadataIdentifier metadataIdentifier = new ServiceMetadataIdentifier(BASE_URL);
+ metadataReport.saveServiceMetadata(metadataIdentifier, BASE_URL);
+ String metadata = metadataReport.getMetadata(metadataIdentifier);
+ assertEquals(URL.encode(BASE_URL.toFullString()), metadata);
+ metadataReport.removeServiceMetadata(metadataIdentifier);
+ assertNull(metadataReport.getMetadata(metadataIdentifier));
+ }
+
+ /**
+ * Test {@link MetadataReport#saveSubscribedData(SubscriberMetadataIdentifier, Collection)} and
+ * {@link MetadataReport#getSubscribedURLs(SubscriberMetadataIdentifier)}
+ */
+ @Test
+ public void testSaveSubscribedDataAndGetSubscribedURLs() {
+ SubscriberMetadataIdentifier metadataIdentifier = new SubscriberMetadataIdentifier(BASE_URL);
+ Set urls = singleton(BASE_URL).stream().map(URL::toIdentityString).collect(toSet());
+ metadataReport.saveSubscribedData(metadataIdentifier, urls);
+ Collection subscribedURLs = metadataReport.getSubscribedURLs(metadataIdentifier);
+ assertEquals(1, subscribedURLs.size());
+ assertEquals(urls, subscribedURLs);
+ }
+
+ /**
+ * Test {@link MetadataReport#saveExportedURLs(SortedSet)},
+ * {@link MetadataReport#getExportedURLsContent(String, String)} and
+ * {@link MetadataReport#getExportedURLs(String, String)}
+ */
+ @Test
+ public void testSaveExportedURLsAndGetExportedURLs() {
+ SortedSet urls = singleton(BASE_URL).stream().map(URL::toIdentityString).collect(TreeSet::new, Set::add, Set::addAll);
+ metadataReport.saveExportedURLs(urls);
+
+ URLRevisionResolver urlRevisionResolver = URLRevisionResolver.INSTANCE;
+ String revision = urlRevisionResolver.resolve(urls);
+ assertEquals(urls, metadataReport.getExportedURLs(APP_NAME, revision));
+ }
+}
diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder b/dubbo-metadata/dubbo-metadata-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder
new file mode 100644
index 00000000000..1b1b7d2b5b6
--- /dev/null
+++ b/dubbo-metadata/dubbo-metadata-api/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.definition.builder.TypeBuilder
@@ -0,0 +1,2 @@
+test=org.apache.dubbo.metadata.definition.TestTypeBuilder
+test3=org.apache.dubbo.metadata.definition.Test3TypeBuilder
diff --git a/dubbo-metadata/dubbo-metadata-report-nacos/src/test/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReportTest.java b/dubbo-metadata/dubbo-metadata-report-nacos/src/test/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReportTest.java
deleted file mode 100644
index 0b36bfb2ef1..00000000000
--- a/dubbo-metadata/dubbo-metadata-report-nacos/src/test/java/org/apache/dubbo/metadata/store/nacos/NacosMetadataReportTest.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * 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.metadata.store.nacos;
-
-import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.NetUtils;
-import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
-import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
-import org.apache.dubbo.metadata.report.identifier.KeyTypeEnum;
-import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
-import org.apache.dubbo.metadata.report.identifier.ServiceMetadataIdentifier;
-import org.apache.dubbo.metadata.report.identifier.SubscriberMetadataIdentifier;
-
-import com.alibaba.nacos.api.config.ConfigService;
-import com.alibaba.nacos.api.exception.NacosException;
-import com.google.gson.Gson;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE;
-import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
-
-//FIXME: waiting for embedded Nacos suport, then we can open the switch.
-@Disabled("https://github.com/alibaba/nacos/issues/1188")
-public class NacosMetadataReportTest {
-
- private static final String SESSION_TIMEOUT_KEY = "session";
-
- private static final String TEST_SERVICE = "org.apache.dubbo.metadata.store.nacos.NacosMetadata4TstService";
-
- private NacosMetadataReport nacosMetadataReport;
-
- private NacosMetadataReportFactory nacosMetadataReportFactory;
-
- private ConfigService configService;
-
- private static final String NACOS_GROUP = "metadata_test";
-
- /**
- * timeout(ms) for nacos session
- */
- private static final int SESSION_TIMEOUT = 15 * 1000;
-
- /**
- * timeout(ms) for query operation on nacos
- */
- private static final int NACOS_READ_TIMEOUT = 5 * 1000;
-
- /**
- * interval(ms) to make nacos cache refresh
- */
- private static final int INTERVAL_TO_MAKE_NACOS_REFRESH = 1000;
-
- /**
- * version for test
- */
- private static final String VERSION = "1.0.0";
-
- /**
- * group for test
- */
- private static final String METADATA_GROUP = null;
-
- /**
- * application name for test
- */
- private static final String APPLICATION_NAME = "nacos-metdata-report-test";
-
- /**
- * revision for test
- */
- private static final String REVISION = "90980";
-
- /**
- * protocol for test
- */
- private static final String PROTOCOL = "xxx";
-
- @BeforeEach
- public void setUp() {
- URL url = URL.valueOf("nacos://127.0.0.1:8848?group=" + NACOS_GROUP)
- .addParameter(SESSION_TIMEOUT_KEY, SESSION_TIMEOUT);
- nacosMetadataReportFactory = new NacosMetadataReportFactory();
- this.nacosMetadataReport = (NacosMetadataReport) nacosMetadataReportFactory.getMetadataReport(url);
- }
-
- @AfterEach
- public void tearDown() throws Exception {
- }
-
-
- @Test
- public void testStoreProvider() throws Exception {
- MetadataIdentifier providerIdentifier =
- storeProvider(nacosMetadataReport, TEST_SERVICE, VERSION, METADATA_GROUP, APPLICATION_NAME);
- String serverContent = configService.getConfig(providerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
- Assertions.assertNotNull(serverContent);
-
- Gson gson = new Gson();
- FullServiceDefinition fullServiceDefinition = gson.fromJson(serverContent, FullServiceDefinition.class);
- Assertions.assertEquals(fullServiceDefinition.getParameters().get("paramTest"), "nacosTest");
-
- //Clear test data
- configService.removeConfig(providerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
- }
-
- @Test
- public void testStoreConsumer() throws Exception {
- MetadataIdentifier consumerIdentifier = storeConsumer(nacosMetadataReport, TEST_SERVICE, VERSION, METADATA_GROUP, APPLICATION_NAME);
-
- String serverContent = configService.getConfig(consumerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
- Assertions.assertNotNull(serverContent);
- Assertions.assertEquals(serverContent, "{\"paramConsumerTest\":\"nacosConsumer\"}");
-
- //clear test data
- configService.removeConfig(consumerIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
- }
-
- @Test
- public void testDoSaveServiceMetadata() throws Exception {
- URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
- "?paramTest=nacosTest&version=" + VERSION + "&application="
- + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
- ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(TEST_SERVICE, VERSION,
- METADATA_GROUP, "provider", REVISION, PROTOCOL);
- nacosMetadataReport.doSaveMetadata(serviceMetadataIdentifier, url);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
- String serviceMetaData = configService.getConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
- Assertions.assertNotNull(serviceMetaData);
- Assertions.assertEquals(serviceMetaData, URL.encode(url.toFullString()));
-
- //clear test data
- configService.removeConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
- }
-
- @Test
- public void testDoRemoveServiceMetadata() throws Exception {
- URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
- "?paramTest=nacosTest&version=" + VERSION + "&application="
- + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
- ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(TEST_SERVICE, VERSION,
- METADATA_GROUP, "provider", REVISION, PROTOCOL);
- nacosMetadataReport.doSaveMetadata(serviceMetadataIdentifier, url);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
- String serviceMetaData = configService.getConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
- Assertions.assertNotNull(serviceMetaData);
-
- nacosMetadataReport.doRemoveMetadata(serviceMetadataIdentifier);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
- serviceMetaData = configService.getConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
- Assertions.assertNull(serviceMetaData);
- }
-
- @Test
- public void testDoGetExportedURLs() throws InterruptedException, NacosException {
- URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
- "?paramTest=nacosTest&version=" + VERSION + "&application="
- + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
- ServiceMetadataIdentifier serviceMetadataIdentifier = new ServiceMetadataIdentifier(TEST_SERVICE, VERSION,
- METADATA_GROUP, "provider", REVISION, PROTOCOL);
-
- nacosMetadataReport.doSaveMetadata(serviceMetadataIdentifier, url);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-
- List exportedURLs = nacosMetadataReport.doGetExportedURLs(serviceMetadataIdentifier);
- Assertions.assertTrue(exportedURLs.size() == 1);
-
- String exportedUrl = exportedURLs.get(0);
- Assertions.assertNotNull(exportedUrl);
- Assertions.assertEquals(exportedUrl, url.toFullString());
-
- //clear test data
- configService.removeConfig(serviceMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
- }
-
- @Test
- public void testDoSaveSubscriberData() throws InterruptedException, NacosException {
- URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + TEST_SERVICE +
- "?paramTest=nacosTest&version=" + VERSION + "&application="
- + APPLICATION_NAME + (METADATA_GROUP == null ? "" : "&group=" + METADATA_GROUP));
- SubscriberMetadataIdentifier subscriberMetadataIdentifier = new SubscriberMetadataIdentifier(APPLICATION_NAME, REVISION);
- Gson gson = new Gson();
- String urlListJsonString = gson.toJson(Arrays.asList(url));
- nacosMetadataReport.doSaveSubscriberData(subscriberMetadataIdentifier, urlListJsonString);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
-
- String subscriberMetadata = configService.getConfig(subscriberMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP, NACOS_READ_TIMEOUT);
- Assertions.assertNotNull(subscriberMetadata);
- Assertions.assertEquals(subscriberMetadata, urlListJsonString);
-
- //clear test data
- configService.removeConfig(subscriberMetadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), NACOS_GROUP);
-
- }
-
- private MetadataIdentifier storeProvider(NacosMetadataReport nacosMetadataReport, String interfaceName, String version,
- String group, String application)
- throws ClassNotFoundException, InterruptedException {
- URL url = URL.valueOf("xxx://" + NetUtils.getLocalAddress().getHostName() + ":4444/" + interfaceName +
- "?paramTest=nacosTest&version=" + version + "&application="
- + application + (group == null ? "" : "&group=" + group));
-
- MetadataIdentifier providerMetadataIdentifier =
- new MetadataIdentifier(interfaceName, version, group, PROVIDER_SIDE, application);
- Class interfaceClass = Class.forName(interfaceName);
- FullServiceDefinition fullServiceDefinition =
- ServiceDefinitionBuilder.buildFullDefinition(interfaceClass, url.getParameters());
-
- nacosMetadataReport.storeProviderMetadata(providerMetadataIdentifier, fullServiceDefinition);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
- return providerMetadataIdentifier;
- }
-
- private MetadataIdentifier storeConsumer(NacosMetadataReport nacosMetadataReport, String interfaceName,
- String version, String group, String application) throws InterruptedException {
- MetadataIdentifier consumerIdentifier = new MetadataIdentifier(interfaceName, version, group, CONSUMER_SIDE, application);
- Map tmp = new HashMap<>();
- tmp.put("paramConsumerTest", "nacosConsumer");
- nacosMetadataReport.storeConsumerMetadata(consumerIdentifier, tmp);
- Thread.sleep(INTERVAL_TO_MAKE_NACOS_REFRESH);
- return consumerIdentifier;
- }
-
-}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
index e0e70eaa09f..d599891bf46 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactory.java
@@ -18,10 +18,11 @@
import org.apache.dubbo.metadata.MetadataService;
import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getExportedServicesRevision;
/**
* base class for remote and local implementations.
@@ -30,12 +31,28 @@
*/
abstract class BaseMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
- private final Map proxies = new HashMap<>();
+ private final ConcurrentMap proxiesCache = new ConcurrentHashMap<>();
public final MetadataService getProxy(ServiceInstance serviceInstance) {
- return proxies.computeIfAbsent(serviceInstance.getServiceName() + "##" +
- ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance), id -> createProxy(serviceInstance));
+ return proxiesCache.computeIfAbsent(createProxyCacheKey(serviceInstance), id -> createProxy(serviceInstance));
+ }
+
+ /**
+ * Create the cache key of the proxy of {@link MetadataService}
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @return non-null
+ * @since 2.7.8
+ */
+ protected String createProxyCacheKey(ServiceInstance serviceInstance) {
+ return serviceInstance.getServiceName() + "#" + getExportedServicesRevision(serviceInstance);
}
+ /**
+ * Create the instance proxy of {@link MetadataService}
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @return non-null
+ */
protected abstract MetadataService createProxy(ServiceInstance serviceInstance);
}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java
index 51a64d70c8a..f8c95d6fc10 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactory.java
@@ -24,6 +24,7 @@
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
import static java.lang.String.format;
@@ -35,10 +36,12 @@
*
* @since 2.7.8
*/
-public class CompositeMetadataServiceProxyFactory implements MetadataServiceProxyFactory {
+public class CompositeMetadataServiceProxyFactory extends BaseMetadataServiceProxyFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(CompositeMetadataServiceProxyFactory.class);
@Override
- public MetadataService getProxy(ServiceInstance serviceInstance) {
+ public MetadataService createProxy(ServiceInstance serviceInstance) {
MetadataService metadataService = (MetadataService) newProxyInstance(
getClass().getClassLoader(),
new Class[]{MetadataService.class},
@@ -49,25 +52,58 @@ public MetadataService getProxy(ServiceInstance serviceInstance) {
static class MetadataServiceInvocationHandler implements InvocationHandler {
- private final List metadataServices;
+ private final ServiceInstance serviceInstance;
+
+ private final MetadataServiceProxyFactory excluded;
- private final Logger logger = LoggerFactory.getLogger(getClass());
+ private volatile List metadataServices;
MetadataServiceInvocationHandler(ServiceInstance serviceInstance,
MetadataServiceProxyFactory excluded) {
- this.metadataServices = initMetadataServices(serviceInstance, excluded);
+ this.serviceInstance = serviceInstance;
+ this.excluded = excluded;
}
- private List initMetadataServices(ServiceInstance serviceInstance,
- MetadataServiceProxyFactory excluded) {
+ private List loadMetadataServices() {
return getExtensionLoader(MetadataServiceProxyFactory.class)
.getSupportedExtensionInstances()
.stream()
- .filter(factory -> !factory.equals(excluded))
- .map(factory -> factory.getProxy(serviceInstance))
+ .filter(this::isRequiredFactory)
+ .map(this::getProxy)
+ .filter(Objects::nonNull)
.collect(Collectors.toList());
}
+ private List getMetadataServices() {
+ if (metadataServices == null) {
+ metadataServices = loadMetadataServices();
+ if (metadataServices.isEmpty()) {
+ throw new IllegalStateException(format("No valid proxy of %s can't be loaded.",
+ MetadataService.class.getName()));
+ }
+ }
+ return metadataServices;
+ }
+
+ private boolean isRequiredFactory(MetadataServiceProxyFactory factory) {
+ return !factory.equals(excluded);
+ }
+
+ private MetadataService getProxy(MetadataServiceProxyFactory factory) {
+ MetadataService metadataService = null;
+ try {
+ metadataService = factory.getProxy(serviceInstance);
+ } catch (Exception e) {
+ if (logger.isErrorEnabled()) {
+ logger.error(format("The proxy of %s can't be gotten by %s [from : %s].",
+ MetadataService.class.getName(),
+ factory.getClass().getName(),
+ serviceInstance.toString()));
+ }
+ }
+ return metadataService;
+ }
+
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
@@ -77,7 +113,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
Object result = null;
- for (MetadataService metadataService : metadataServices) {
+ for (MetadataService metadataService : getMetadataServices()) {
try {
result = method.invoke(metadataService, args);
if (result != null) {
@@ -85,7 +121,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
}
} catch (Exception e) {
if (logger.isErrorEnabled()) {
- logger.error(format("MetadataService[type : %s] executes failed", metadataService.getClass().getName()), e);
+ logger.error(format("MetadataService[type : %s] executes failed.", metadataService.getClass().getName()), e);
}
}
}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
index 736108f02dd..01bde1d1ebd 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java
@@ -748,7 +748,7 @@ void stop() {
@Override
protected void notifyOverrides() {
// to notify configurator/router changes
- directory.refreshInvoker(Collections.emptyList());
+ directory.refreshOverrideAndInvoker(Collections.emptyList());
}
}
@@ -769,7 +769,7 @@ void removeNotifyListener(RegistryDirectory listener) {
@Override
protected void notifyOverrides() {
- listeners.forEach(listener -> listener.refreshInvoker(Collections.emptyList()));
+ listeners.forEach(listener -> listener.refreshOverrideAndInvoker(Collections.emptyList()));
}
}
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java
index 735ef18594b..b7e03c417a7 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryInvokerWrapper.java
@@ -22,18 +22,17 @@
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Cluster;
+import org.apache.dubbo.rpc.cluster.ClusterInvoker;
-class RegistryInvokerWrapper implements Invoker {
+class RegistryInvokerWrapper implements ClusterInvoker {
private RegistryDirectory