You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by li...@apache.org on 2021/08/31 02:50:33 UTC

[dubbo] branch 3.0 updated: [3.0] add bootstrap test (#8639)

This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 3f59150  [3.0] add bootstrap test (#8639)
3f59150 is described below

commit 3f591501a68c43e2798adb51ae362d6ca55e8f3b
Author: ken.lj <ke...@gmail.com>
AuthorDate: Tue Aug 31 10:50:20 2021 +0800

    [3.0] add bootstrap test (#8639)
---
 dubbo-config/dubbo-config-api/pom.xml              |   7 +
 .../dubbo/config/bootstrap/DubboBootstrap.java     |  84 ++++--------
 .../dubbo/config/bootstrap/DubboBootstrapTest.java | 147 ++++++++++++++++++++-
 .../metadata/ServiceInstanceMetadataUtils.java     |  32 ++++-
 4 files changed, 208 insertions(+), 62 deletions(-)

diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml
index 665d14d..ad4220b 100644
--- a/dubbo-config/dubbo-config-api/pom.xml
+++ b/dubbo-config/dubbo-config-api/pom.xml
@@ -172,5 +172,12 @@
             </exclusions>
         </dependency>
 
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>1.15.3</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 </project>
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
index a502ce8..19d924d 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java
@@ -68,10 +68,8 @@ import org.apache.dubbo.metadata.report.MetadataReportInstance;
 import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
 import org.apache.dubbo.registry.client.DefaultServiceInstance;
 import org.apache.dubbo.registry.client.ServiceInstance;
-import org.apache.dubbo.registry.client.metadata.MetadataUtils;
 import org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils;
 import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
-import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.AbstractRegistryFactory;
 import org.apache.dubbo.rpc.Protocol;
 import org.apache.dubbo.rpc.model.ApplicationModel;
@@ -88,6 +86,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Condition;
@@ -113,7 +112,6 @@ import static org.apache.dubbo.common.utils.StringUtils.isNotEmpty;
 import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_PUBLISH_DELAY;
 import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PUBLISH_DELAY_KEY;
 import static org.apache.dubbo.metadata.WritableMetadataService.getDefaultExtension;
-import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.calInstanceRevision;
 import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.setMetadataStorageType;
 import static org.apache.dubbo.registry.support.AbstractRegistryFactory.getServiceDiscoveries;
 import static org.apache.dubbo.remoting.Constants.CLIENT_KEY;
@@ -128,7 +126,7 @@ import static org.apache.dubbo.remoting.Constants.CLIENT_KEY;
  *
  * @since 2.7.5
  */
-public class DubboBootstrap {
+public final class DubboBootstrap {
 
     public static final String DEFAULT_REGISTRY_ID = "REGISTRY#DEFAULT";
 
@@ -161,43 +159,44 @@ public class DubboBootstrap {
     private final ExecutorService executorService = newSingleThreadExecutor();
 
     private final ExecutorRepository executorRepository = getExtensionLoader(ExecutorRepository.class).getDefaultExtension();
-    ;
 
-    private final ConfigManager configManager;
+    protected ScheduledFuture<?> asyncMetadataFuture;
 
-    private final Environment environment;
+    protected final ConfigManager configManager;
 
-    private ReferenceConfigCache cache;
+    protected final Environment environment;
 
-    private AtomicBoolean initialized = new AtomicBoolean(false);
+    protected ReferenceConfigCache cache;
 
-    private AtomicBoolean started = new AtomicBoolean(false);
+    protected AtomicBoolean initialized = new AtomicBoolean(false);
 
-    private AtomicBoolean startup = new AtomicBoolean(true);
+    protected AtomicBoolean started = new AtomicBoolean(false);
 
-    private AtomicBoolean destroyed = new AtomicBoolean(false);
+    protected AtomicBoolean startup = new AtomicBoolean(true);
 
-    private AtomicBoolean shutdown = new AtomicBoolean(false);
+    protected AtomicBoolean destroyed = new AtomicBoolean(false);
 
-    private volatile boolean isCurrentlyInStart = false;
+    protected AtomicBoolean shutdown = new AtomicBoolean(false);
 
-    private volatile ServiceInstance serviceInstance;
+    protected volatile boolean isCurrentlyInStart = false;
 
-    private volatile MetadataService metadataService;
+    protected volatile ServiceInstance serviceInstance;
 
-    private volatile MetadataServiceExporter metadataServiceExporter;
+    protected volatile MetadataService metadataService;
 
-    private List<ServiceConfigBase<?>> exportedServices = new ArrayList<>();
+    protected volatile MetadataServiceExporter metadataServiceExporter;
 
-    private final List<CompletableFuture<?>> asyncExportingFutures = new ArrayList<>();
+    protected List<ServiceConfigBase<?>> exportedServices = new ArrayList<>();
 
-    private final List<CompletableFuture<?>> asyncReferringFutures = new ArrayList<>();
+    protected final List<CompletableFuture<?>> asyncExportingFutures = new ArrayList<>();
 
-    private volatile boolean asyncExportFinish = true;
+    protected final List<CompletableFuture<?>> asyncReferringFutures = new ArrayList<>();
 
-    private volatile boolean asyncReferFinish = true;
+    protected volatile boolean asyncExportFinish = true;
 
-    private static boolean ignoreConfigState;
+    protected volatile boolean asyncReferFinish = true;
+
+    protected static boolean ignoreConfigState;
 
     /**
      * See {@link ApplicationModel} and {@link ExtensionLoader} for why DubboBootstrap is designed to be singleton.
@@ -1505,7 +1504,7 @@ public class DubboBootstrap {
         }
     }
 
-    private void registerServiceInstance() {
+    protected void registerServiceInstance() {
         if (this.serviceInstance != null) {
             return;
         }
@@ -1515,14 +1514,14 @@ public class DubboBootstrap {
         ServiceInstance serviceInstance = createServiceInstance(serviceName);
         boolean registered = true;
         try {
-            doRegisterServiceInstance(serviceInstance);
+            ServiceInstanceMetadataUtils.registerMetadataAndInstance(serviceInstance);
         } catch (Exception e) {
             registered = false;
             logger.error("Register instance error", e);
         }
         if(registered){
             // scheduled task for updating Metadata and ServiceInstance
-            executorRepository.nextScheduledExecutor().scheduleAtFixedRate(() -> {
+            asyncMetadataFuture = executorRepository.nextScheduledExecutor().scheduleAtFixedRate(() -> {
                 InMemoryWritableMetadataService localMetadataService = (InMemoryWritableMetadataService) WritableMetadataService.getDefaultExtension();
                 localMetadataService.blockUntilUpdated();
                 try {
@@ -1536,36 +1535,6 @@ public class DubboBootstrap {
         }
     }
 
-    private void doRegisterServiceInstance(ServiceInstance serviceInstance) {
-        // register instance only when at least one service is exported.
-        if (serviceInstance.getPort() > 0) {
-            if (REMOTE_METADATA_STORAGE_TYPE.equals(ServiceInstanceMetadataUtils.getMetadataStorageType(serviceInstance))) {
-                publishMetadataToRemote(serviceInstance);
-            }
-            logger.info("Start registering instance address to registry.");
-            getServiceDiscoveries().forEach(serviceDiscovery ->
-            {
-                ServiceInstance serviceInstanceForRegistry = new DefaultServiceInstance((DefaultServiceInstance) serviceInstance);
-                calInstanceRevision(serviceDiscovery, serviceInstanceForRegistry);
-                if (logger.isDebugEnabled()) {
-                    logger.info("Start registering instance address to registry" + serviceDiscovery.getUrl() + ", instance " + serviceInstanceForRegistry);
-                }
-                // register metadata
-                serviceDiscovery.register(serviceInstanceForRegistry);
-            });
-        }
-    }
-
-    private void publishMetadataToRemote(ServiceInstance serviceInstance) {
-//        InMemoryWritableMetadataService localMetadataService = (InMemoryWritableMetadataService)WritableMetadataService.getDefaultExtension();
-//        localMetadataService.blockUntilUpdated();
-        if (logger.isInfoEnabled()) {
-            logger.info("Start publishing metadata to remote center, this only makes sense for applications enabled remote metadata center.");
-        }
-        RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService();
-        remoteMetadataService.publishMetadata(serviceInstance.getServiceName());
-    }
-
     private void unregisterServiceInstance() {
         if (serviceInstance != null) {
             getServiceDiscoveries().forEach(serviceDiscovery -> {
@@ -1595,6 +1564,9 @@ public class DubboBootstrap {
                         unexportMetadataService();
                         unexportServices();
                         unreferServices();
+                        if (asyncMetadataFuture != null) {
+                            asyncMetadataFuture.cancel(true);
+                        }
                     }
 
                     destroyRegistries();
diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java
index 484c315..24915d7 100644
--- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java
+++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java
@@ -20,17 +20,29 @@ import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.constants.CommonConstants;
 import org.apache.dubbo.common.url.component.ServiceConfigURL;
 import org.apache.dubbo.common.utils.ConfigUtils;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.AbstractInterfaceConfig;
 import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.MetadataReportConfig;
 import org.apache.dubbo.config.MonitorConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
 import org.apache.dubbo.config.ServiceConfig;
 import org.apache.dubbo.config.SysProps;
-import org.apache.dubbo.config.AbstractInterfaceConfig;
 import org.apache.dubbo.config.api.DemoService;
 import org.apache.dubbo.config.provider.impl.DemoServiceImpl;
 import org.apache.dubbo.config.utils.ConfigValidationUtils;
+import org.apache.dubbo.metadata.MetadataInfo;
+import org.apache.dubbo.metadata.MetadataService;
+import org.apache.dubbo.metadata.WritableMetadataService;
 import org.apache.dubbo.monitor.MonitorService;
 import org.apache.dubbo.registry.RegistryService;
+import org.apache.dubbo.registry.client.metadata.MetadataUtils;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol;
 
+import org.apache.curator.test.TestingServer;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -45,11 +57,18 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Map;
 import java.util.Properties;
 
 import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_MONITOR_ADDRESS;
+import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
 import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY;
 import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_SECONDS_KEY;
+import static org.apache.dubbo.rpc.model.ApplicationModel.getApplicationConfig;
+import static org.hamcrest.CoreMatchers.anything;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.is;
 
 /**
  * {@link DubboBootstrap} Test
@@ -59,10 +78,20 @@ import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_SE
 public class DubboBootstrapTest {
 
     private static File dubboProperties;
+    private static TestingServer server;
+    private static int zkServerPort = NetUtils.getAvailablePort(NetUtils.getRandomPort());
+    private static String zkServerAddress = "zookeeper://127.0.0.1:" + zkServerPort;
 
     @BeforeAll
     public static void setUp(@TempDir Path folder) {
         DubboBootstrap.reset();
+        try {
+            server = new TestingServer(zkServerPort, true);
+            server.start();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Assertions.fail(e.getMessage());
+        }
         dubboProperties = folder.resolve(CommonConstants.DUBBO_PROPERTIES_KEY).toFile();
         System.setProperty(CommonConstants.DUBBO_PROPERTIES_KEY, dubboProperties.getAbsolutePath());
     }
@@ -70,11 +99,18 @@ public class DubboBootstrapTest {
     @AfterAll
     public static void tearDown() {
         System.clearProperty(CommonConstants.DUBBO_PROPERTIES_KEY);
+        try {
+            server.stop();
+        } catch (IOException e) {
+            e.printStackTrace();
+            Assertions.fail(e.getMessage());
+        }
     }
 
     @AfterEach
     public void afterEach() throws IOException {
         DubboBootstrap.reset();
+        ApplicationModel.reset();
         SysProps.clear();
     }
 
@@ -209,6 +245,115 @@ public class DubboBootstrapTest {
         return interfaceConfig;
     }
 
+    @Test
+    public void testBootstrapStart() {
+        ServiceConfig<DemoService> service = new ServiceConfig<>();
+        service.setInterface(DemoService.class);
+        service.setRef(new DemoServiceImpl());
+
+        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
+        bootstrap.application(new ApplicationConfig("bootstrap-test"))
+            .registry(new RegistryConfig(zkServerAddress))
+            .protocol(new ProtocolConfig(CommonConstants.DUBBO_PROTOCOL, -1))
+            .service(service)
+            .start();
+
+        Assertions.assertTrue(bootstrap.isInitialized());
+        Assertions.assertTrue(bootstrap.isStarted());
+        Assertions.assertFalse(bootstrap.isShutdown());
+
+        Assertions.assertNotNull(bootstrap.serviceInstance);
+        Assertions.assertTrue(bootstrap.exportedServices.size() > 0);
+        Assertions.assertNotNull(bootstrap.asyncMetadataFuture);
+    }
+
+    @Test
+    public void testLocalMetadataServiceExporter() {
+        ServiceConfig<DemoService> service = new ServiceConfig<>();
+        service.setInterface(DemoService.class);
+        service.setRef(new DemoServiceImpl());
+
+        int availablePort = NetUtils.getAvailablePort();
+
+        ApplicationConfig applicationConfig = new ApplicationConfig("bootstrap-test");
+        applicationConfig.setMetadataServicePort(availablePort);
+        DubboBootstrap bootstrap = DubboBootstrap.getInstance();
+        bootstrap.application(applicationConfig)
+            .registry(new RegistryConfig(zkServerAddress))
+            .protocol(new ProtocolConfig(CommonConstants.DUBBO_PROTOCOL, -1))
+            .service(service)
+            .start();
+
+        assertMetadataService(bootstrap, availablePort, false);
+    }
+
+    @Test
+    public void testRemoteMetadataServiceExporter() {
+        ServiceConfig<DemoService> service = new ServiceConfig<>();
+        service.setInterface(DemoService.class);
+        service.setRef(new DemoServiceImpl());
+
+        int availablePort = NetUtils.getAvailablePort();
+
+        ApplicationConfig applicationConfig = new ApplicationConfig("bootstrap-test");
+        applicationConfig.setMetadataServicePort(availablePort);
+        applicationConfig.setMetadataType(REMOTE_METADATA_STORAGE_TYPE);
+
+        RegistryConfig registryConfig = new RegistryConfig(zkServerAddress);
+        registryConfig.setUseAsMetadataCenter(false);
+        registryConfig.setUseAsConfigCenter(false);
+
+        Exception exception = null;
+        try {
+            DubboBootstrap.getInstance()
+                .application(applicationConfig)
+                .registry(registryConfig)
+                .protocol(new ProtocolConfig(CommonConstants.DUBBO_PROTOCOL, -1))
+                .service(service)
+                .start();
+        } catch (Exception e) {
+            exception = e;
+            DubboBootstrap.reset();
+        }
+
+        Assertions.assertNotNull(exception);
+
+        DubboBootstrap.getInstance()
+            .application(applicationConfig)
+            .registry(registryConfig)
+            .protocol(new ProtocolConfig(CommonConstants.DUBBO_PROTOCOL, -1))
+            .service(service)
+            .metadataReport(new MetadataReportConfig(zkServerAddress))
+            .start();
+
+        assertMetadataService(DubboBootstrap.getInstance(), availablePort, true);
+
+    }
+
+    private void assertMetadataService(DubboBootstrap bootstrap, int availablePort, boolean shouldReport) {
+        Assertions.assertTrue(bootstrap.metadataServiceExporter.isExported());
+        DubboProtocol protocol = DubboProtocol.getDubboProtocol();
+        Map<String, Exporter<?>> exporters = protocol.getExporterMap();
+        Assertions.assertEquals(2, exporters.size());
+
+        ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
+        serviceConfig.setRegistry(new RegistryConfig("N/A"));
+        serviceConfig.setInterface(MetadataService.class);
+        serviceConfig.setGroup(getApplicationConfig().getName());
+        serviceConfig.setVersion(MetadataService.VERSION);
+        assertThat(exporters, hasEntry(is(serviceConfig.getUniqueServiceName() + ":" + availablePort), anything()));
+
+        WritableMetadataService metadataService = MetadataUtils.getLocalMetadataService();
+        MetadataInfo metadataInfo = metadataService.getDefaultMetadataInfo();
+        Assertions.assertNotNull(metadataInfo);
+        if (shouldReport) {
+            Assertions.assertTrue(metadataInfo.hasReported());
+        } else {
+            Assertions.assertFalse(metadataInfo.hasReported());
+        }
+    }
+
+
     private void writeDubboProperties(String key, String value) {
         OutputStream os = null;
         try {
diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
index e4f2e90..5c0e8ea 100644
--- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
+++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java
@@ -32,7 +32,6 @@ import org.apache.dubbo.registry.client.ServiceInstanceCustomizer;
 import org.apache.dubbo.registry.client.metadata.store.InMemoryWritableMetadataService;
 import org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl;
 import org.apache.dubbo.registry.support.AbstractRegistryFactory;
-import org.apache.dubbo.rpc.model.ApplicationModel;
 
 import com.google.gson.Gson;
 
@@ -53,6 +52,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
 import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_CLUSTER_KEY;
 import static org.apache.dubbo.common.utils.StringUtils.isBlank;
 import static org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol.DEFAULT_REGISTER_PROVIDER_KEYS;
+import static org.apache.dubbo.registry.support.AbstractRegistryFactory.getServiceDiscoveries;
 import static org.apache.dubbo.rpc.Constants.DEPRECATED_KEY;
 
 /**
@@ -246,11 +246,26 @@ public class ServiceInstanceMetadataUtils {
         instance.getExtendParams().remove(INSTANCE_REVISION_UPDATED_KEY);
     }
 
-    public static void refreshMetadataAndInstance(ServiceInstance serviceInstance) {
-        if (REMOTE_METADATA_STORAGE_TYPE.equals(ServiceInstanceMetadataUtils.getMetadataStorageType(serviceInstance))) {
-            RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService();
-            remoteMetadataService.publishMetadata(ApplicationModel.getName());
+    public static void registerMetadataAndInstance(ServiceInstance serviceInstance) {
+        // register instance only when at least one service is exported.
+        if (serviceInstance.getPort() > 0) {
+            reportMetadataToRemote(serviceInstance);
+            LOGGER.info("Start registering instance address to registry.");
+            getServiceDiscoveries().forEach(serviceDiscovery ->
+            {
+                ServiceInstance serviceInstanceForRegistry = new DefaultServiceInstance((DefaultServiceInstance) serviceInstance);
+                calInstanceRevision(serviceDiscovery, serviceInstanceForRegistry);
+                if (LOGGER.isDebugEnabled()) {
+                    LOGGER.info("Start registering instance address to registry" + serviceDiscovery.getUrl() + ", instance " + serviceInstanceForRegistry);
+                }
+                // register metadata
+                serviceDiscovery.register(serviceInstanceForRegistry);
+            });
         }
+    }
+
+    public static void refreshMetadataAndInstance(ServiceInstance serviceInstance) {
+        reportMetadataToRemote(serviceInstance);
 
         AbstractRegistryFactory.getServiceDiscoveries().forEach(serviceDiscovery -> {
             ServiceInstance instance = serviceDiscovery.getLocalInstance();
@@ -277,6 +292,13 @@ public class ServiceInstanceMetadataUtils {
         });
     }
 
+    private static void reportMetadataToRemote(ServiceInstance serviceInstance) {
+        if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(getMetadataStorageType(serviceInstance))) {
+            RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService();
+            remoteMetadataService.publishMetadata(serviceInstance.getServiceName());
+        }
+    }
+
     /**
      * Set the default parameters via the specified {@link URL providerURL}
      *