diff --git a/dubbo-all/pom.xml b/dubbo-all/pom.xml index 5c2936e7ccd..050ae247471 100644 --- a/dubbo-all/pom.xml +++ b/dubbo-all/pom.xml @@ -981,6 +981,14 @@ + + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter + + + diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java index e5ecb157c05..948827a1725 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Cluster.java @@ -17,7 +17,9 @@ package org.apache.dubbo.rpc.cluster; import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.cluster.support.FailoverCluster; @@ -29,8 +31,9 @@ * Fault-Tolerant * */ -@SPI(FailoverCluster.NAME) +@SPI(Cluster.DEFAULT) public interface Cluster { + String DEFAULT = FailoverCluster.NAME; /** * Merge the directory invokers to a virtual invoker. @@ -43,4 +46,14 @@ public interface Cluster { @Adaptive Invoker join(Directory 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 directory; private Cluster cluster; private Invoker invoker; - private URL url; - public RegistryInvokerWrapper(RegistryDirectory directory, Cluster cluster, Invoker invoker, URL url) { + public RegistryInvokerWrapper(RegistryDirectory directory, Cluster cluster, Invoker invoker) { this.directory = directory; this.cluster = cluster; this.invoker = invoker; - this.url = url; } @Override @@ -48,11 +47,7 @@ public Result invoke(Invocation invocation) throws RpcException { @Override public URL getUrl() { - return url; - } - - public void setUrl(URL url) { - this.url = url; + return invoker.getUrl(); } public void setInvoker(Invoker invoker) { @@ -76,4 +71,9 @@ public boolean isAvailable() { public void destroy() { invoker.destroy(); } + + @Override + public URL getRegistryUrl() { + return directory.getUrl(); + } } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java index 76da303d7ca..9a4acc8f761 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryProtocol.java @@ -43,6 +43,7 @@ import org.apache.dubbo.rpc.cluster.Cluster; import org.apache.dubbo.rpc.cluster.Configurator; import org.apache.dubbo.rpc.cluster.governance.GovernanceRuleRepository; +import org.apache.dubbo.rpc.cluster.support.MergeableCluster; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.ProviderModel; import org.apache.dubbo.rpc.protocol.InvokerWrapper; @@ -132,7 +133,6 @@ public class RegistryProtocol implements Protocol { //To solve the problem of RMI repeated exposure port conflicts, the services that have been exposed are no longer exposed. //providerurl <--> exporter private final ConcurrentMap> bounds = new ConcurrentHashMap<>(); - private Cluster cluster; private Protocol protocol; private RegistryFactory registryFactory; private ProxyFactory proxyFactory; @@ -152,10 +152,6 @@ private static String[] getFilteredKeys(URL url) { } } - public void setCluster(Cluster cluster) { - this.cluster = cluster; - } - public void setProtocol(Protocol protocol) { this.protocol = protocol; } @@ -221,12 +217,13 @@ public Exporter export(final Invoker originInvoker) throws RpcExceptio // register stated url on provider model registerStatedUrl(registryUrl, registeredProviderUrl, register); - // Deprecated! Subscribe to override rules in 2.6.x or before. - registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); exporter.setRegisterUrl(registeredProviderUrl); exporter.setSubscribeUrl(overrideSubscribeUrl); + // Deprecated! Subscribe to override rules in 2.6.x or before. + registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); + notifyExport(exporter); //Ensure that a new exporter instance is returned every time export return new DestroyableExporter<>(exporter); @@ -449,14 +446,12 @@ public Invoker refer(Class type, URL url) throws RpcException { String group = qs.get(GROUP_KEY); if (group != null && group.length() > 0) { if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) { - return doRefer(getMergeableCluster(), registry, type, url); + return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url); } } - return doRefer(cluster, registry, type, url); - } - private Cluster getMergeableCluster() { - return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension("mergeable"); + Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY)); + return doRefer(cluster, registry, type, url); } private Invoker doRefer(Cluster cluster, Registry registry, Class type, URL url) { @@ -479,7 +474,7 @@ private Invoker doRefer(Cluster cluster, Registry registry, Class type return invoker; } - RegistryInvokerWrapper registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl); + RegistryInvokerWrapper registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker); for (RegistryProtocolListener listener : listeners) { listener.onRefer(this, registryInvokerWrapper); } @@ -504,7 +499,6 @@ public void reRefer(Invoker invoker, URL newSubscribeUrl) { directory.subscribe(toSubscribeUrl(newSubscribeUrl)); invokerWrapper.setInvoker(invokerWrapper.getCluster().join(directory)); - invokerWrapper.setUrl(newSubscribeUrl); } private static URL toSubscribeUrl(URL url) { diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java index e55b223bf6f..85b63de95dc 100644 --- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java @@ -20,8 +20,11 @@ import org.junit.jupiter.api.Test; import static java.lang.String.valueOf; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -64,4 +67,10 @@ public void testSetAndGetValues() { assertFalse(instance.isHealthy()); assertFalse(instance.getMetadata().isEmpty()); } + + @Test + public void testGetMetadata() { + assertNotNull(instance.getMetadata(METADATA_SERVICE_URLS_PROPERTY_NAME)); + assertNotNull(instance.getMetadata(METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME)); + } } diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactoryTest.java new file mode 100644 index 00000000000..6aca4c64eff --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/BaseMetadataServiceProxyFactoryTest.java @@ -0,0 +1,78 @@ +/* + * 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.registry.client.metadata.proxy; + +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.DefaultServiceInstance; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static java.lang.String.valueOf; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * {@link BaseMetadataServiceProxyFactory} Test-Cases + * + * @since 2.7.8 + */ +public class BaseMetadataServiceProxyFactoryTest { + + private MyMetadataServiceProxyFactory factory; + + private DefaultServiceInstance instance; + + @BeforeEach + public void init() { + factory = new MyMetadataServiceProxyFactory(); + instance = createServiceInstance(); + } + + private DefaultServiceInstance createServiceInstance() { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(valueOf(System.nanoTime()), "A", "127.0.0.1", 8080); + Map metadata = new HashMap<>(); + metadata.put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, "X"); + serviceInstance.setMetadata(metadata); + return serviceInstance; + } + + @Test + public void testCreateProxyCacheKey() { + assertEquals("A#X", factory.createProxyCacheKey(instance)); + } + + @Test + public void testCreateProxy() { + MetadataService metadataService = factory.createProxy(instance); + MetadataService metadataService2 = factory.createProxy(instance); + assertNotSame(metadataService, metadataService2); + } + + @Test + public void testGetProxy() { + MetadataService metadataService = factory.getProxy(instance); + MetadataService metadataService2 = factory.getProxy(instance); + assertSame(metadataService, metadataService2); + } + +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactoryTest.java new file mode 100644 index 00000000000..ddd78e04b04 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/CompositeMetadataServiceProxyFactoryTest.java @@ -0,0 +1,96 @@ +/* + * 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.registry.client.metadata.proxy; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.metadata.report.MetadataReportInstance; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; + +import static java.lang.String.valueOf; +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.metadata.report.support.Constants.SYNC_REPORT_KEY; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_PROPERTY_NAME; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link CompositeMetadataServiceProxyFactory} Test-Cases + * + * @since 2.7.8 + */ +public class CompositeMetadataServiceProxyFactoryTest { + + private static final URL METADATA_REPORT_URL = URL.valueOf("file://") + .addParameter(APPLICATION_KEY, "test") + .addParameter(SYNC_REPORT_KEY, "true"); + + private static final String APP_NAME = "test-service"; + + private MetadataServiceProxyFactory factory; + + private DefaultServiceInstance instance; + + @BeforeEach + public void init() { + ApplicationModel.getConfigManager().setApplication(new ApplicationConfig(APP_NAME)); + MetadataReportInstance.init(METADATA_REPORT_URL); + factory = MetadataServiceProxyFactory.getExtension(COMPOSITE_METADATA_STORAGE_TYPE); + instance = createServiceInstance(); + } + + @AfterEach + public void reset() throws Exception { + ApplicationModel.reset(); + MetadataReportInstance.getMetadataReport().close(); + } + + private DefaultServiceInstance createServiceInstance() { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(valueOf(System.nanoTime()), "A", "127.0.0.1", 8080); + Map metadata = new HashMap<>(); + metadata.put(EXPORTED_SERVICES_REVISION_PROPERTY_NAME, "X"); + metadata.put(METADATA_SERVICE_URL_PARAMS_PROPERTY_NAME, "{\"dubbo\":{\"application\":\"dubbo-provider-demo\",\"deprecated\":\"false\",\"group\":\"dubbo-provider-demo\",\"version\":\"1.0.0\",\"timestamp\":\"1564845042651\",\"dubbo\":\"2.0.2\",\"host\":\"192.168.0.102\",\"port\":\"20880\"}}"); + serviceInstance.setMetadata(metadata); + return serviceInstance; + } + + @Test + public void testGetProxy() { + MetadataService metadataService = factory.getProxy(instance); + MetadataService metadataService2 = factory.getProxy(instance); + assertSame(metadataService, metadataService2); + } + + @Test + public void testGetExportedURLs() { + MetadataService metadataService = factory.getProxy(instance); + SortedSet exportedURLs = metadataService.getExportedURLs(); + assertTrue(exportedURLs.isEmpty()); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactoryTest.java new file mode 100644 index 00000000000..2c31c225947 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MetadataServiceProxyFactoryTest.java @@ -0,0 +1,49 @@ +/* + * 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.registry.client.metadata.proxy; + +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.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory.getDefaultExtension; +import static org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory.getExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link MetadataServiceProxyFactory} Test-Cases + * + * @since 2.7.8 + */ +public class MetadataServiceProxyFactoryTest { + + @Test + public void testExtension() { + MetadataServiceProxyFactory defaultFactory = getDefaultExtension(); + MetadataServiceProxyFactory factory = getExtension(DEFAULT_METADATA_STORAGE_TYPE); + assertEquals(defaultFactory, factory); + + assertEquals(MyMetadataServiceProxyFactory.class, factory.getClass()); + + factory = getExtension(REMOTE_METADATA_STORAGE_TYPE); + assertEquals(RemoteMetadataServiceProxyFactory.class, factory.getClass()); + + factory = getExtension(COMPOSITE_METADATA_STORAGE_TYPE); + assertEquals(CompositeMetadataServiceProxyFactory.class, factory.getClass()); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ExportingMetadataServiceListener.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MyMetadataServiceProxyFactory.java similarity index 53% rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ExportingMetadataServiceListener.java rename to dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MyMetadataServiceProxyFactory.java index 15faea19f5e..86247e1155c 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ExportingMetadataServiceListener.java +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/proxy/MyMetadataServiceProxyFactory.java @@ -14,29 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.registry.client.event.listener; +package org.apache.dubbo.registry.client.metadata.proxy; -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.event.EventListener; import org.apache.dubbo.metadata.MetadataService; -import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; +import org.apache.dubbo.metadata.store.InMemoryWritableMetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; -/** - * An {@link EventListener} of {@link ServiceInstancePreRegisteredEvent} for - * Exporting the {@link MetadataService} - * - * @see EventListener - * @see ServiceInstancePreRegisteredEvent - * @see MetadataService - * @since 2.7.8 - */ -public class ExportingMetadataServiceListener implements EventListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); +public class MyMetadataServiceProxyFactory extends BaseMetadataServiceProxyFactory { @Override - public void onEvent(ServiceInstancePreRegisteredEvent event) { - + protected MetadataService createProxy(ServiceInstance serviceInstance) { + return new InMemoryWritableMetadataService(); } } diff --git a/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/dubbo/org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory b/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/dubbo/org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory new file mode 100644 index 00000000000..6d2b9dd19c0 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/dubbo/org.apache.dubbo.registry.client.metadata.proxy.MetadataServiceProxyFactory @@ -0,0 +1,2 @@ +# Override "local" implementation +local=org.apache.dubbo.registry.client.metadata.proxy.MyMetadataServiceProxyFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java index d6490762031..990646ae6c0 100644 --- a/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java +++ b/dubbo-registry/dubbo-registry-consul/src/main/java/org/apache/dubbo/registry/consul/ConsulRegistry.java @@ -45,8 +45,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -65,6 +65,7 @@ import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.SERVICE_TAG; import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.URL_META_KEY; import static org.apache.dubbo.registry.consul.AbstractConsulRegistry.WATCH_TIMEOUT; +import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; /** * registry center implementation for consul @@ -78,15 +79,20 @@ public class ConsulRegistry extends FailbackRegistry { new NamedThreadFactory("dubbo-consul-notifier", true)); private ConcurrentMap notifiers = new ConcurrentHashMap<>(); private ScheduledExecutorService ttlConsulCheckExecutor; + /** + * The ACL token + */ + private String token; public ConsulRegistry(URL url) { super(url); + token = url.getParameter(TOKEN_KEY, (String) null); String host = url.getHost(); int port = url.getPort() != 0 ? url.getPort() : DEFAULT_PORT; client = new ConsulClient(host, port); checkPassInterval = url.getParameter(CHECK_PASS_INTERVAL, DEFAULT_CHECK_PASS_INTERVAL); - ttlConsulCheckExecutor = Executors.newSingleThreadScheduledExecutor(); + ttlConsulCheckExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("Ttl-Consul-Check-Executor", true)); ttlConsulCheckExecutor.scheduleAtFixedRate(this::checkPass, checkPassInterval / 8, checkPassInterval / 8, TimeUnit.MILLISECONDS); } @@ -102,7 +108,11 @@ public void register(URL url) { @Override public void doRegister(URL url) { - client.agentServiceRegister(buildService(url)); + if (token == null) { + client.agentServiceRegister(buildService(url)); + } else { + client.agentServiceRegister(buildService(url), token); + } } @Override @@ -116,7 +126,11 @@ public void unregister(URL url) { @Override public void doUnregister(URL url) { - client.agentServiceDeregister(buildId(url)); + if (token == null) { + client.agentServiceDeregister(buildId(url)); + } else { + client.agentServiceDeregister(buildId(url), token); + } } @Override @@ -198,12 +212,16 @@ private void checkPass() { for (URL url : getRegistered()) { String checkId = buildId(url); try { - client.agentCheckPass("service:" + checkId); + if (token == null) { + client.agentCheckPass("service:" + checkId); + } else { + client.agentCheckPass("service:" + checkId, null, token); + } if (logger.isDebugEnabled()) { logger.debug("check pass for url: " + url + " with check id: " + checkId); } } catch (Throwable t) { - logger.warn("fail to check pass for url: " + url + ", check id is: " + checkId); + logger.warn("fail to check pass for url: " + url + ", check id is: " + checkId, t); } } } @@ -213,6 +231,7 @@ private Response> getHealthServices(String service, long ind .setTag(SERVICE_TAG) .setQueryParams(new QueryParams(watchTimeout, index)) .setPassing(true) + .setToken(token) .build(); return client.getHealthServices(service, request); } @@ -220,6 +239,7 @@ private Response> getHealthServices(String service, long ind private Response>> getAllServices(long index, int watchTimeout) { CatalogServicesRequest request = CatalogServicesRequest.newBuilder() .setQueryParams(new QueryParams(watchTimeout, index)) + .setToken(token) .build(); return client.getCatalogServices(request); } diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java index 7167d9c35e2..8d08b3dfdbb 100644 --- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java +++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryProtocolTest.java @@ -30,7 +30,6 @@ import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Protocol; import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.cluster.support.FailfastCluster; import org.apache.dubbo.rpc.model.ApplicationModel; import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.protocol.AbstractInvoker; @@ -86,7 +85,7 @@ public void testDefaultPort() { public void testExportUrlNull() { Assertions.assertThrows(IllegalArgumentException.class, () -> { RegistryProtocol registryProtocol = getRegistryProtocol(); - registryProtocol.setCluster(new FailfastCluster()); +// registryProtocol.setCluster(new FailfastCluster()); Protocol dubboProtocol = DubboProtocol.getDubboProtocol(); registryProtocol.setProtocol(dubboProtocol); @@ -99,7 +98,7 @@ public void testExportUrlNull() { @Test public void testExport() { RegistryProtocol registryProtocol = getRegistryProtocol(); - registryProtocol.setCluster(new FailfastCluster()); +// registryProtocol.setCluster(new FailfastCluster()); registryProtocol.setRegistryFactory(ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension()); Protocol dubboProtocol = DubboProtocol.getDubboProtocol(); diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java index d4ca4b34679..b301a784e5b 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.remoting.transport.netty4; -import io.netty.channel.socket.SocketChannel; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; @@ -37,6 +36,7 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.SocketChannel; import io.netty.handler.timeout.IdleStateHandler; import java.net.InetSocketAddress; @@ -149,8 +149,8 @@ protected void doClose() throws Throwable { } try { if (bootstrap != null) { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); + bossGroup.shutdownGracefully().syncUninterruptibly(); + workerGroup.shutdownGracefully().syncUninterruptibly(); } } catch (Throwable e) { logger.warn(e.getMessage(), e); diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java index 4cbe8361fb5..74ac50cad44 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolFilterWrapper.java @@ -18,6 +18,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.utils.UrlUtils; import org.apache.dubbo.rpc.Exporter; @@ -38,6 +39,7 @@ /** * ListenerProtocol */ +@Activate(order = 100) public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java index a28906f7a15..af943c36c60 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/ProtocolListenerWrapper.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.utils.UrlUtils; import org.apache.dubbo.rpc.Exporter; @@ -38,6 +39,7 @@ /** * ListenerProtocol */ +@Activate(order = 200) public class ProtocolListenerWrapper implements Protocol { private final Protocol protocol;