From 5fb8dc07a85faf7c31bd8d2cf84c2d598784d1e9 Mon Sep 17 00:00:00 2001 From: Mercy Ma Date: Mon, 17 Jun 2019 11:12:47 +0800 Subject: [PATCH] Introduce new Service Discovery model (#4223) --- dubbo-all/pom.xml | 34 + dubbo-bootstrap/pom.xml | 94 + .../dubbo/bootstrap/AbstractSettings.java | 36 + .../dubbo/bootstrap/ApplicationSettings.java | 127 + .../dubbo/bootstrap/DubboBootstrap.java | 644 +++++ .../dubbo/bootstrap/ProtocolSettings.java | 215 ++ .../dubbo/bootstrap/ReferenceSettings.java | 334 +++ .../dubbo/bootstrap/RegistrySettings.java | 164 ++ .../dubbo/bootstrap/ServiceSettings.java | 384 +++ .../org/apache/dubbo/bootstrap/Settings.java | 32 + .../dubbo/bootstrap/DubboBootstrapTest.java | 96 + .../DubboServiceConsumerBootstrap.java | 58 + .../DubboServiceProviderBootstrap.java | 52 + .../apache/dubbo/bootstrap/EchoService.java | 31 + .../dubbo/bootstrap/EchoServiceImpl.java | 36 + .../src/test/resources/log4j.properties | 7 + .../router/condition/ConditionRouter.java | 10 +- .../rpc/cluster/support/ClusterUtils.java | 2 +- .../rpc/cluster/support/ClusterUtilsTest.java | 2 +- .../java/org/apache/dubbo/common/URL.java | 60 +- .../common/constants/RegistryConstants.java | 34 + .../common/function/ThrowableConsumer.java | 66 + .../common/function/ThrowableFunction.java | 71 + .../dubbo/common/utils/DefaultPage.java | 82 + .../apache/dubbo/common/utils/NetUtils.java | 2 +- .../org/apache/dubbo/common/utils/Page.java | 87 + .../dubbo/common/utils/ReflectUtils.java | 2307 +++++++++-------- .../apache/dubbo/common/utils/UrlUtils.java | 10 +- .../dubbo/common/utils/DefaultPageTest.java | 37 + .../apache/dubbo/filter/LegacyInvocation.java | 4 +- dubbo-config/dubbo-config-api/pom.xml | 48 +- .../dubbo/config/AbstractInterfaceConfig.java | 26 +- .../dubbo/config/DubboShutdownHook.java | 8 +- .../apache/dubbo/config/ProtocolConfig.java | 4 +- .../apache/dubbo/config/ReferenceConfig.java | 1355 +++++----- .../apache/dubbo/config/RegistryConfig.java | 4 +- .../apache/dubbo/config/ServiceConfig.java | 2135 +++++++-------- .../config/builders/AbstractBuilder.java | 13 +- .../config/builders/ProtocolBuilder.java | 8 +- .../config/builders/ReferenceBuilder.java | 4 + .../config/builders/RegistryBuilder.java | 8 +- .../dubbo/config/builders/ServiceBuilder.java | 10 +- .../event/DubboServiceDestroyedEvent.java | 32 + .../event/ReferenceConfigDestroyedEvent.java | 41 + .../ReferenceConfigInitializedEvent.java | 48 + .../event/ServiceConfigExportedEvent.java | 36 + .../event/ServiceConfigUnexportedEvent.java | 36 + .../event/listener/LoggingEventListener.java | 51 + .../listener/ServiceNameMappingListener.java | 55 + .../ConfigurableMetadataServiceExporter.java | 131 + .../ServiceInstancePortCustomizer.java | 53 + .../org.apache.dubbo.event.EventListener | 2 + ....registry.client.ServiceInstanceCustomizer | 1 + .../dubbo/config/DubboConsumerBootstrap.java | 63 + .../dubbo/config/DubboProviderBootstrap.java | 84 + .../dubbo/config/ReferenceConfigTest.java | 48 +- .../dubbo/config/RegistryConfigTest.java | 2 +- .../dubbo/config/ServiceConfigTest.java | 49 +- .../apache/dubbo/config/api/DemoService.java | 3 + .../ServiceNameMappingListenerTest.java | 63 + ...nfigurableMetadataServiceExporterTest.java | 88 + .../config/provider/impl/DemoServiceImpl.java | 17 +- .../config/spring/SimpleRegistryExporter.java | 4 +- .../configcenter/DynamicConfiguration.java | 72 +- .../support/nop/NopDynamicConfiguration.java | 21 +- .../nop/NopDynamicConfigurationTest.java | 70 + .../ZookeeperDynamicConfiguration.java | 79 +- .../ZookeeperDynamicConfigurationTest.java | 39 + dubbo-dependencies-bom/pom.xml | 13 +- .../dubbo-dependencies-zookeeper/pom.xml | 2 +- dubbo-event/pom.xml | 45 + .../dubbo/event/AbstractEventDispatcher.java | 155 ++ .../dubbo/event/DirectEventDispatcher.java | 30 + .../java/org/apache/dubbo/event/Event.java | 49 + .../apache/dubbo/event/EventDispatcher.java | 66 + .../org/apache/dubbo/event/EventListener.java | 124 + .../org/apache/dubbo/event/GenericEvent.java | 34 + .../dubbo/event/GenericEventListener.java | 130 + .../org/apache/dubbo/event/Listenable.java | 131 + .../dubbo/event/ParallelEventDispatcher.java | 32 + .../org.apache.dubbo.event.EventDispatcher | 2 + .../dubbo/event/AbstractEventListener.java | 40 + .../event/DirectEventDispatcherTest.java | 153 ++ .../org/apache/dubbo/event/EchoEvent.java | 29 + .../apache/dubbo/event/EchoEventListener.java | 32 + .../dubbo/event/EchoEventListener2.java | 61 + .../dubbo/event/EventDispatcherTest.java | 45 + .../apache/dubbo/event/EventListenerTest.java | 44 + .../dubbo/event/GenericEventListenerTest.java | 77 + .../apache/dubbo/event/GenericEventTest.java | 41 + .../event/ParallelEventDispatcherTest.java | 59 + .../org.apache.dubbo.event.EventListener | 1 + .../identifier/MetadataIdentifierTest.java | 1 - .../store/redis/RedisMetadataReport.java | 2 +- dubbo-metadata/pom.xml | 66 + ...ynamicConfigurationServiceNameMapping.java | 103 + .../InMemoryLocalMetadataService.java | 192 ++ .../dubbo/metadata/LocalMetadataService.java | 88 + .../dubbo/metadata/MetadataService.java | 151 ++ .../metadata/MetadataServiceExporter.java | 44 + .../dubbo/metadata/ServiceNameMapping.java | 64 + ...apache.dubbo.metadata.LocalMetadataService | 1 + ...g.apache.dubbo.metadata.ServiceNameMapping | 1 + ...icConfigurationServiceNameMappingTest.java | 114 + .../InMemoryLocalMetadataServiceTest.java | 140 + .../metadata/LocalMetadataServiceTest.java | 34 + .../dubbo/monitor/support/MonitorFilter.java | 2 +- .../monitor/dubbo/DubboMonitorFactory.java | 2 +- dubbo-registry/dubbo-registry-api/pom.xml | 15 +- .../client/DefaultServiceInstance.java | 142 + .../EventPublishingServiceDiscovery.java | 293 +++ ...ventPublishingServiceDiscoveryFactory.java | 47 + .../registry/client/ServiceDiscovery.java | 201 ++ .../client/ServiceDiscoveryFactory.java | 49 + .../registry/client/ServiceInstance.java | 94 + .../client/ServiceInstanceCustomizer.java | 38 + .../ServiceInstanceMetadataCustomizer.java | 73 + .../event/ServiceDiscoveryStartedEvent.java | 49 + .../event/ServiceDiscoveryStartingEvent.java | 48 + .../event/ServiceDiscoveryStoppedEvent.java | 48 + .../event/ServiceDiscoveryStoppingEvent.java | 48 + .../client/event/ServiceInstanceEvent.java | 47 + .../ServiceInstancePreRegisteredEvent.java | 34 + .../ServiceInstancePreUnregisteredEvent.java | 34 + .../event/ServiceInstanceRegisteredEvent.java | 34 + .../ServiceInstanceUnregisteredEvent.java | 35 + .../event/ServiceInstancesChangedEvent.java | 64 + .../CustomizableServiceInstanceListener.java | 54 + .../event/listener/LoggingEventListener.java | 85 + .../ServiceInstancesChangedListener.java | 36 + .../DefaultMetadataServiceProxyFactory.java | 42 + ...tedServicesRevisionMetadataCustomizer.java | 69 + .../client/metadata/MetadataServiceProxy.java | 114 + .../metadata/MetadataServiceProxyFactory.java | 51 + .../metadata/MetadataServiceURLBuilder.java | 81 + ...ataServiceURLParamsMetadataCustomizer.java | 60 + .../ServiceInstanceMetadataUtils.java | 172 ++ .../dubbo/registry/client/package-info.java | 23 + .../RandomServiceInstanceSelector.java | 47 + .../selector/ServiceInstanceSelector.java | 43 + .../integration/RegistryDirectory.java | 10 +- .../integration/RegistryProtocol.java | 36 +- .../support/AbstractRegistryFactory.java | 9 +- .../support/ServiceOrientedRegistry.java | 472 ++++ ...bo.registry.client.ServiceDiscoveryFactory | 1 + ...lient.metadata.MetadataServiceProxyFactory | 1 + ...ry.client.selector.ServiceInstanceSelector | 1 + .../org.apache.dubbo.event.EventListener | 2 + ....registry.client.ServiceInstanceCustomizer | 2 + .../client/DefaultServiceInstanceTest.java | 60 + .../EventPublishingServiceDiscoveryTest.java | 164 ++ .../client/InMemoryServiceDiscovery.java | 110 + .../InMemoryServiceDiscoveryFactory.java | 33 + .../client/ServiceDiscoveryFactoryTest.java | 55 + .../registry/client/ServiceDiscoveryTest.java | 264 ++ .../ServiceInstanceMetadataUtilsTest.java | 74 + .../support/ServiceOrientedRegistryTest.java | 173 ++ ...bo.registry.client.ServiceDiscoveryFactory | 1 + .../registry/dubbo/DubboRegistryFactory.java | 10 +- .../registry/dubbo/RegistryDirectoryTest.java | 14 +- .../dubbo/SimpleRegistryExporter.java | 4 +- .../dubbo/registry/nacos/NacosRegistry.java | 2 +- .../registry/nacos/NacosRegistryFactory.java | 70 +- .../registry/nacos/NacosServiceDiscovery.java | 136 + .../nacos/NacosServiceDiscoveryFactory.java | 39 + .../nacos/util/NacosNamingServiceUtils.java | 156 ++ ...bo.registry.client.ServiceDiscoveryFactory | 1 + .../dubbo-registry-zookeeper/pom.xml | 4 + .../registry/zookeeper/ZookeeperInstance.java | 77 + .../zookeeper/ZookeeperServiceDiscovery.java | 159 ++ ...ookeeperServiceDiscoveryChangeWatcher.java | 68 + .../ZookeeperServiceDiscoveryFactory.java | 39 + .../util/CuratorFrameworkParams.java | 102 + .../zookeeper/util/CuratorFrameworkUtils.java | 122 + ...bo.registry.client.ServiceDiscoveryFactory | 1 + ...bo.registry.client.ServiceDiscoveryFactory | 1 + .../zookeeper/ZookeeperRegistryTest.java | 2 +- .../ZookeeperServiceDiscoveryTest.java | 206 ++ .../support/header/HeaderExchangeClient.java | 4 +- .../support/header/HeartBeatTaskTest.java | 1 + .../transport/netty4/NettyClient.java | 4 +- .../transport/netty4/ClientReconnectTest.java | 1 + .../dubbo/rpc/filter/ContextFilter.java | 2 +- .../apache/dubbo/rpc/support/MockInvoker.java | 6 +- .../dubbo/rpc/filter/GenericFilterTest.java | 6 +- .../rpc/filter/GenericImplFilterTest.java | 5 +- .../protocol/dubbo/CallbackServiceCodec.java | 8 +- .../dubbo/DecodeableRpcInvocation.java | 2 +- .../dubbo/rpc/protocol/dubbo/DubboCodec.java | 2 +- .../rpc/protocol/dubbo/DubboProtocol.java | 20 +- .../ReferenceCountExchangeClientTest.java | 2 +- .../dubbo/rpc/protocol/http/HttpProtocol.java | 2 +- dubbo-rpc/dubbo-rpc-xml/README.md | 2 +- .../rpc/protocol/xmlrpc/XmlRpcProtocol.java | 18 +- pom.xml | 5 +- 195 files changed, 14261 insertions(+), 3109 deletions(-) create mode 100644 dubbo-bootstrap/pom.xml create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/AbstractSettings.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ApplicationSettings.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ProtocolSettings.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ReferenceSettings.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/RegistrySettings.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ServiceSettings.java create mode 100644 dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/Settings.java create mode 100644 dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboBootstrapTest.java create mode 100644 dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java create mode 100644 dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java create mode 100644 dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoService.java create mode 100644 dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoServiceImpl.java create mode 100644 dubbo-bootstrap/src/test/resources/log4j.properties create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableConsumer.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/utils/DefaultPage.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/utils/Page.java create mode 100644 dubbo-common/src/test/java/org/apache/dubbo/common/utils/DefaultPageTest.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/DubboServiceDestroyedEvent.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/LoggingEventListener.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java create mode 100644 dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstancePortCustomizer.java create mode 100644 dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener create mode 100644 dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer create mode 100644 dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboConsumerBootstrap.java create mode 100644 dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboProviderBootstrap.java create mode 100644 dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListenerTest.java create mode 100644 dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java create mode 100644 dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java create mode 100644 dubbo-event/pom.xml create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/AbstractEventDispatcher.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/DirectEventDispatcher.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/Event.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/EventDispatcher.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/EventListener.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/GenericEvent.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/GenericEventListener.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/Listenable.java create mode 100644 dubbo-event/src/main/java/org/apache/dubbo/event/ParallelEventDispatcher.java create mode 100644 dubbo-event/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventDispatcher create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/AbstractEventListener.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/DirectEventDispatcherTest.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/EchoEvent.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener2.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/EventDispatcherTest.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/EventListenerTest.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventListenerTest.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventTest.java create mode 100644 dubbo-event/src/test/java/org/apache/dubbo/event/ParallelEventDispatcherTest.java create mode 100644 dubbo-event/src/test/resources/META-INF/services/org.apache.dubbo.event.EventListener create mode 100644 dubbo-metadata/pom.xml create mode 100644 dubbo-metadata/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java create mode 100644 dubbo-metadata/src/main/java/org/apache/dubbo/metadata/InMemoryLocalMetadataService.java create mode 100644 dubbo-metadata/src/main/java/org/apache/dubbo/metadata/LocalMetadataService.java create mode 100644 dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataService.java create mode 100644 dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java create mode 100644 dubbo-metadata/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java create mode 100644 dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.LocalMetadataService create mode 100644 dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.ServiceNameMapping create mode 100644 dubbo-metadata/src/test/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMappingTest.java create mode 100644 dubbo-metadata/src/test/java/org/apache/dubbo/metadata/InMemoryLocalMetadataServiceTest.java create mode 100644 dubbo-metadata/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryFactory.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstance.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceMetadataCustomizer.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartedEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartingEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppedEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppingEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancesChangedEvent.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/DefaultMetadataServiceProxyFactory.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxy.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxyFactory.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLBuilder.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/package-info.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/RandomServiceInstanceSelector.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/ServiceInstanceSelector.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/ServiceOrientedRegistry.java create mode 100644 dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory create mode 100644 dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.MetadataServiceProxyFactory create mode 100644 dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.selector.ServiceInstanceSelector create mode 100644 dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener create mode 100644 dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscoveryFactory.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactoryTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java create mode 100644 dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory create mode 100644 dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java create mode 100644 dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryFactory.java create mode 100644 dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java create mode 100644 dubbo-registry/dubbo-registry-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperInstance.java create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryFactory.java create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkParams.java create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory create mode 100644 dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java diff --git a/dubbo-all/pom.xml b/dubbo-all/pom.xml index d8c8949c13..eaf1772246 100644 --- a/dubbo-all/pom.xml +++ b/dubbo-all/pom.xml @@ -507,6 +507,23 @@ true + + + org.apache.dubbo + dubbo-event + ${project.version} + compile + true + + + + org.apache.dubbo + dubbo-metadata + ${project.version} + compile + true + + org.springframework @@ -639,6 +656,10 @@ org.apache.dubbo:dubbo-metadata-report-nacos org.apache.dubbo:dubbo-serialization-native-hession org.apache.dubbo:dubbo-rpc-native-thrift + + + org.apache.dubbo:dubbo-event + org.apache.dubbo:dubbo-metadata @@ -764,6 +785,19 @@ META-INF/dubbo/internal/org.apache.dubbo.metadata.store.MetadataReportFactory + + + META-INF/dubbo/internal/org.apache.dubbo.event.EventDispatcher + + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataServiceExporter + + + + META-INF/dubbo/internal/org.apache.dubbo.metadata.LocalMetadataService + + diff --git a/dubbo-bootstrap/pom.xml b/dubbo-bootstrap/pom.xml new file mode 100644 index 0000000000..ef3cad3f05 --- /dev/null +++ b/dubbo-bootstrap/pom.xml @@ -0,0 +1,94 @@ + + + + org.apache.dubbo + dubbo-parent + ${revision} + ../pom.xml + + 4.0.0 + + dubbo-bootstrap + jar + + dubbo-bootstrap + The bootstrap module of Dubbo project + + + + + org.apache.dubbo + dubbo-config-api + ${project.parent.version} + + + + + + + org.apache.dubbo + dubbo-registry-zookeeper + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${project.parent.version} + test + + + + + org.apache.dubbo + dubbo-registry-nacos + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-configcenter-nacos + ${project.parent.version} + test + + + + com.alibaba.nacos + nacos-client + test + + + + org.apache.dubbo + dubbo-rpc-dubbo + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.parent.version} + test + + + + org.apache.curator + curator-test + test + + + + + \ No newline at end of file diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/AbstractSettings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/AbstractSettings.java new file mode 100644 index 0000000000..6bb9c976bc --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/AbstractSettings.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.bootstrap; + +/** + * Abstract {@link Settings} + * + * @since 2.7.3 + */ +public class AbstractSettings implements Settings { + + private final DubboBootstrap dubboBootstrap; + + public AbstractSettings(DubboBootstrap dubboBootstrap) { + this.dubboBootstrap = dubboBootstrap; + } + + @Override + public DubboBootstrap next() { + return dubboBootstrap; + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ApplicationSettings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ApplicationSettings.java new file mode 100644 index 0000000000..a9bc9c9467 --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ApplicationSettings.java @@ -0,0 +1,127 @@ +/* + * 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.bootstrap; + +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.MonitorConfig; +import org.apache.dubbo.config.builders.ApplicationBuilder; + +import java.util.Map; + +/** + * {@link ApplicationConfig Application} settings + * + * @since 2.7.3 + */ +public class ApplicationSettings extends AbstractSettings { + + private final ApplicationBuilder builder; + + public ApplicationSettings(ApplicationBuilder builder, DubboBootstrap dubboBootstrap) { + super(dubboBootstrap); + this.builder = builder; + } + + public ApplicationSettings version(String version) { + builder.version(version); + return this; + } + + public ApplicationSettings owner(String owner) { + builder.owner(owner); + return this; + } + + public ApplicationSettings organization(String organization) { + builder.organization(organization); + return this; + } + + public ApplicationSettings architecture(String architecture) { + builder.architecture(architecture); + return this; + } + + public ApplicationSettings environment(String environment) { + builder.environment(environment); + return this; + } + + public ApplicationSettings compiler(String compiler) { + builder.compiler(compiler); + return this; + } + + public ApplicationSettings logger(String logger) { + builder.logger(logger); + return this; + } + + public ApplicationSettings monitor(MonitorConfig monitor) { + builder.monitor(monitor); + return this; + } + + public ApplicationSettings monitor(String monitor) { + builder.monitor(monitor); + return this; + } + + public ApplicationSettings isDefault(Boolean isDefault) { + builder.isDefault(isDefault); + return this; + } + + public ApplicationSettings dumpDirectory(String dumpDirectory) { + builder.dumpDirectory(dumpDirectory); + return this; + } + + public ApplicationSettings qosEnable(Boolean qosEnable) { + builder.qosEnable(qosEnable); + return this; + } + + public ApplicationSettings qosPort(Integer qosPort) { + builder.qosPort(qosPort); + return this; + } + + public ApplicationSettings qosAcceptForeignIp(Boolean qosAcceptForeignIp) { + builder.qosAcceptForeignIp(qosAcceptForeignIp); + return this; + } + + public ApplicationSettings shutwait(String shutwait) { + builder.shutwait(shutwait); + return this; + } + + public ApplicationSettings appendParameter(String key, String value) { + builder.appendParameter(key, value); + return this; + } + + public ApplicationSettings appendParameters(Map appendParameters) { + builder.appendParameters(appendParameters); + return this; + } + + ApplicationConfig build() { + return builder.build(); + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java new file mode 100644 index 0000000000..c6c26062b6 --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/DubboBootstrap.java @@ -0,0 +1,644 @@ +/* + * 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.bootstrap; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.config.AbstractConfig; +import org.apache.dubbo.config.AbstractInterfaceConfig; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.builders.AbstractBuilder; +import org.apache.dubbo.config.builders.ApplicationBuilder; +import org.apache.dubbo.config.builders.ProtocolBuilder; +import org.apache.dubbo.config.builders.ReferenceBuilder; +import org.apache.dubbo.config.builders.RegistryBuilder; +import org.apache.dubbo.config.builders.ServiceBuilder; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.event.EventListener; +import org.apache.dubbo.metadata.MetadataServiceExporter; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.support.ServiceOrientedRegistry; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyMap; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; +import static org.apache.dubbo.common.utils.StringUtils.isBlank; +import static org.apache.dubbo.common.utils.StringUtils.split; +import static org.apache.dubbo.common.utils.StringUtils.trim; +import static org.apache.dubbo.registry.support.AbstractRegistryFactory.getRegistries; + +/** + * The bootstrap class of Dubbo + * + * @since 2.7.3 + */ +public class DubboBootstrap { + + public static final String DEFAULT_REGISTRY_ID = "REGISTRY#DEFAULT"; + + public static final String DEFAULT_PROTOCOL_ID = "PROTOCOL#DEFAULT"; + + public static final String DEFAULT_SERVICE_ID = "SERVICE#DEFAULT"; + + public static final String DEFAULT_REFERENCE_ID = "REFERENCE#DEFAULT"; + + private static final String NAME = DubboBootstrap.class.getSimpleName(); + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final MetadataServiceExporter metadataServiceExporter = new ConfigurableMetadataServiceExporter(); + + private final AtomicBoolean awaited = new AtomicBoolean(false); + + private final Lock lock = new ReentrantLock(); + + private final Condition condition = lock.newCondition(); + + private final ExecutorService executorService = newSingleThreadExecutor(); + + private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + + /** + * Is provider or not + */ + private boolean isProvider; + + private boolean initialized = false; + + private boolean started = false; + + /** + * Only Provider Register + */ + private boolean onlyRegisterProvider = false; + + private ServiceInstance serviceInstance; + + private ApplicationBuilder applicationBuilder; + + private Map registryBuilders = new HashMap<>(); + + private Map protocolBuilders = new HashMap<>(); + + private Map> serviceBuilders = new HashMap<>(); + + private Map> referenceBuilders = new HashMap<>(); + + /** + * The global {@link ApplicationConfig} + */ + private ApplicationConfig applicationConfig; + + /** + * the global {@link RegistryConfig registries} + */ + private Map registryConfigs = emptyMap(); + + /** + * the global {@link RegistryConfig registries} + */ + private Map protocolConfigs = emptyMap(); + + /** + * the global {@link ServiceConfig services} + */ + private Map> serviceConfigs = emptyMap(); + + /** + * the global {@link ReferenceConfig references} + */ + private Map> referenceConfigs = new HashMap<>(); + + public ApplicationSettings application(String name) { + return new ApplicationSettings(initApplicationBuilder(name), this); + } + + public RegistrySettings registry() { + return registry(DEFAULT_REGISTRY_ID); + } + + public RegistrySettings registry(String id) { + return new RegistrySettings(initRegistryBuilder(id), this); + } + + public ProtocolSettings protocol() { + return protocol(DEFAULT_PROTOCOL_ID); + } + + public ProtocolSettings protocol(String id) { + return new ProtocolSettings(initProtocolBuilder(id), this); + } + + public ServiceSettings service(String id) { + return new ServiceSettings(initServiceBuilder(id), this); + } + + public ReferenceSettings reference(String id) { + return new ReferenceSettings<>(initReferenceBuilder(id), this); + } + + /** + * Set only register provider or not + * + * @param onlyRegisterProvider if true, only register the provider and reduce the registries' load. + * @return {@link DubboBootstrap} + */ + public DubboBootstrap onlyRegisterProvider(boolean onlyRegisterProvider) { + this.onlyRegisterProvider = onlyRegisterProvider; + return this; + } + + public DubboBootstrap application(String name, Consumer builder) { + initApplicationBuilder(name); + builder.accept(applicationBuilder); + return this; + } + + public DubboBootstrap registry(String id, Consumer builder) { + builder.accept(initRegistryBuilder(id)); + return this; + } + + public DubboBootstrap protocol(String id, Consumer builder) { + builder.accept(initProtocolBuilder(id)); + return this; + } + + public DubboBootstrap service(String id, Consumer> builder) { + builder.accept(initServiceBuilder(id)); + return this; + } + + public DubboBootstrap reference(String id, Consumer> builder) { + builder.accept(initReferenceBuilder(id)); + return this; + } + + /** + * Initialize + */ + public void init() { + + if (isInitialized()) { + return; + } + + initApplicationConfig(); + + initRegistryConfigs(); + + initProtocolConfigs(); + + initServiceConfigs(); + + initReferenceConfigs(); + + clearBuilders(); + + initialized = true; + + if (logger.isInfoEnabled()) { + logger.info(NAME + " has been initialized!"); + } + } + + /** + * Get the {@link ServiceConfig} by specified id + * + * @param id The {@link ServiceConfig#getId() id} of {@link ServiceConfig} + * @param the type of service interface + * @return null if not found + */ + public ServiceConfig serviceConfig(String id) { + return (ServiceConfig) serviceConfigs.get(id); + } + + /** + * Get the {@link ReferenceConfig} by specified id + * + * @param id The {@link ReferenceConfig#getId() id} of {@link ReferenceConfig} + * @param the type of service interface + * @return null if not found + */ + public ReferenceConfig referenceConfig(String id) { + return (ReferenceConfig) referenceConfigs.get(id); + } + + private List getServiceDiscoveries() { + return getRegistries() + .stream() + .filter(registry -> ServiceOrientedRegistry.class.isInstance(registry)) + .map(registry -> ServiceOrientedRegistry.class.cast(registry)) + .map(ServiceOrientedRegistry::getServiceDiscovery) + .collect(Collectors.toList()); + } + + /** + * Start the bootstrap + */ + public DubboBootstrap start() { + + if (!isStarted()) { + if (!isInitialized()) { + init(); + } + + exportServices(); + + // Not only provider register and some services are exported + if (!onlyRegisterProvider && !serviceConfigs.isEmpty()) { + /** + * export {@link MetadataService} + */ + List exportedURLs = exportMetadataService(applicationConfig, registryConfigs, protocolConfigs); + + /** + * Register the local {@link ServiceInstance} + */ + registerServiceInstance(exportedURLs); + } + + started = true; + + if (logger.isInfoEnabled()) { + logger.info(NAME + " is starting..."); + } + } + return this; + } + + /** + * Block current thread to be await. + * + * @return {@link DubboBootstrap} + */ + public DubboBootstrap await() { + // has been waited, return immediately + if (!awaited.get()) { + if (!executorService.isShutdown()) { + executorService.execute(() -> executeMutually(() -> { + while (!awaited.get()) { + if (logger.isInfoEnabled()) { + logger.info(NAME + " is awaiting..."); + } + try { + condition.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + })); + } + } + return this; + } + + /** + * Stop the bootstrap + */ + public void stop() { + + if (!isInitialized() || !isStarted()) { + return; + } + + unregisterServiceInstance(); + + destroy(); + + clear(); + + release(); + + shutdown(); + } + + public boolean isInitialized() { + return initialized; + } + + public boolean isStarted() { + return started; + } + + private ApplicationBuilder initApplicationBuilder(String name) { + applicationBuilder = new ApplicationBuilder().name(name); + return applicationBuilder; + } + + private RegistryBuilder createRegistryBuilder(String id) { + return new RegistryBuilder().id(id); + } + + private ProtocolBuilder createProtocolBuilder(String id) { + return new ProtocolBuilder().id(id); + } + + private ServiceBuilder createServiceBuilder(String id) { + return new ServiceBuilder().id(id); + } + + private ReferenceBuilder createReferenceBuilder(String id) { + return new ReferenceBuilder().id(id); + } + + private RegistryBuilder initRegistryBuilder(String id) { + return registryBuilders.computeIfAbsent(id, this::createRegistryBuilder); + } + + private ProtocolBuilder initProtocolBuilder(String id) { + return protocolBuilders.computeIfAbsent(id, this::createProtocolBuilder); + } + + private ServiceBuilder initServiceBuilder(String id) { + return serviceBuilders.computeIfAbsent(id, this::createServiceBuilder); + } + + private ReferenceBuilder initReferenceBuilder(String id) { + return referenceBuilders.computeIfAbsent(id, this::createReferenceBuilder); + } + + private void initApplicationConfig() { + this.applicationConfig = buildApplicationConfig(); + } + + private void initRegistryConfigs() { + this.registryConfigs = buildRegistryConfigs(); + } + + private void initProtocolConfigs() { + this.protocolConfigs = buildProtocolConfigs(); + } + + private void initReferenceConfigs() { + this.referenceConfigs = buildReferenceConfigs(); + this.referenceConfigs.values().forEach(this::initReferenceConfig); + } + + /** + * Add an instance of {@link EventListener} + * + * @param listener {@link EventListener} + * @return {@link DubboBootstrap} + */ + public DubboBootstrap addEventListener(EventListener listener) { + eventDispatcher.addEventListener(listener); + return this; + } + + private void initServiceConfigs() { + this.serviceConfigs = buildServiceConfigs(); + this.serviceConfigs.values().forEach(this::initServiceConfig); + } + + private List exportMetadataService(ApplicationConfig applicationConfig, + Map globalRegistryConfigs, + Map globalProtocolConfigs) { + ConfigurableMetadataServiceExporter exporter = new ConfigurableMetadataServiceExporter(); + exporter.setApplicationConfig(applicationConfig); + exporter.setRegistries(globalRegistryConfigs.values()); + exporter.setProtocols(globalProtocolConfigs.values()); + return exporter.export(); + } + + private ApplicationConfig buildApplicationConfig() { + return applicationBuilder.build(); + } + + private Map buildProtocolConfigs() { + return buildConfigs(protocolBuilders); + } + + private Map buildRegistryConfigs() { + return buildConfigs(registryBuilders); + } + + private Map> buildServiceConfigs() { + return buildConfigs(serviceBuilders); + } + + private Map> buildReferenceConfigs() { + return buildConfigs(referenceBuilders); + } + + private void exportServices() { + serviceConfigs.values().forEach(this::exportServiceConfig); + } + + private void initServiceConfig(ServiceConfig serviceConfig) { + initConfig(serviceConfig); + initProtocols(serviceConfig); + } + + private void initReferenceConfig(ReferenceConfig referenceConfig) { + initConfig(referenceConfig); + } + + private void initConfig(AbstractInterfaceConfig config) { + initApplication(config); + initRegistries(config); + } + + private void initApplication(AbstractInterfaceConfig config) { + if (config.getApplication() == null) { + config.setApplication(applicationConfig); + } + } + + private void initRegistries(AbstractInterfaceConfig config) { + List registries = config.getRegistries(); + if (CollectionUtils.isEmpty(registries)) { // If no registry present + registries = new LinkedList<>(); + String registerIds = config.getRegistryIds(); + if (!isBlank(registerIds)) { + for (String id : split(registerIds, ',')) { + RegistryConfig registryConfig = registryConfigs.get(trim(id)); + registries.add(registryConfig); + } + } + if (registries.isEmpty()) { // If empty, add all global registries + registries.addAll(registryConfigs.values()); + } + + config.setRegistries(registries); + } + } + + private void initProtocols(ServiceConfig serviceConfig) { + List protocols = serviceConfig.getProtocols(); + if (CollectionUtils.isEmpty(protocols)) { // If no protocols present + protocols = new LinkedList<>(); + String protocolIds = serviceConfig.getProtocolIds(); + if (!isBlank(protocolIds)) { + for (String id : split(protocolIds, ',')) { + ProtocolConfig protocol = protocolConfigs.get(trim(id)); + protocols.add(protocol); + } + } + if (protocols.isEmpty()) { // If empty, add all global protocols + protocols.addAll(protocolConfigs.values()); + } + serviceConfig.setProtocols(protocols); + } + } + + private void exportServiceConfig(ServiceConfig serviceConfig) { + serviceConfig.export(); + } + + private void registerServiceInstance(List exportedURLs) { + + exportedURLs + .stream() + .findFirst() + .ifPresent(url -> { + String serviceName = url.getParameter(APPLICATION_KEY); + String host = url.getHost(); + int port = url.getPort(); + + ServiceInstance serviceInstance = initServiceInstance(serviceName, host, port); + + getServiceDiscoveries().forEach(serviceDiscovery -> serviceDiscovery.register(serviceInstance)); + + }); + } + + private void unregisterServiceInstance() { + + if (serviceInstance != null) { + getServiceDiscoveries().forEach(serviceDiscovery -> { + serviceDiscovery.unregister(serviceInstance); + }); + } + + } + + private ServiceInstance initServiceInstance(String serviceName, String host, int port) { + this.serviceInstance = new DefaultServiceInstance(serviceName, host, port); + return this.serviceInstance; + } + + private void destroy() { + + destroyProtocolConfigs(); + + destroyReferenceConfigs(); + + } + + private void destroyProtocolConfigs() { + protocolConfigs.values().forEach(ProtocolConfig::destroy); + if (logger.isDebugEnabled()) { + logger.debug(NAME + "'s all ProtocolConfigs have been destroyed."); + } + } + + private void destroyReferenceConfigs() { + referenceConfigs.values().forEach(ReferenceConfig::destroy); + if (logger.isDebugEnabled()) { + logger.debug(NAME + "'s all ReferenceConfigs have been destroyed."); + } + } + + private void clear() { + + clearBuilders(); + + clearConfigs(); + + ConfigManager.getInstance().clear(); + } + + private void clearConfigs() { + this.applicationConfig = null; + this.registryConfigs.clear(); + this.protocolConfigs.clear(); + this.serviceConfigs.clear(); + this.referenceConfigs.clear(); + if (logger.isDebugEnabled()) { + logger.debug(NAME + "'s configs have been clear."); + } + } + + private void clearBuilders() { + this.applicationBuilder = null; + this.registryBuilders.clear(); + this.protocolBuilders.clear(); + this.serviceBuilders.clear(); + this.referenceBuilders.clear(); + if (logger.isDebugEnabled()) { + logger.debug(NAME + "'s builders have been clear."); + } + } + + private void release() { + executeMutually(() -> { + while (awaited.compareAndSet(false, true)) { + if (logger.isInfoEnabled()) { + logger.info(NAME + " is about to shutdown..."); + } + condition.signalAll(); + } + }); + } + + private void shutdown() { + if (!executorService.isShutdown()) { + // Shutdown executorService + executorService.shutdown(); + } + } + + private void executeMutually(Runnable runnable) { + try { + lock.lock(); + runnable.run(); + } finally { + lock.unlock(); + } + } + + private static Map buildConfigs(Map map) { + Map configs = new HashMap<>(); + map.entrySet().forEach(entry -> { + configs.put(entry.getKey(), (C) entry.getValue().build()); + }); + return configs; + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ProtocolSettings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ProtocolSettings.java new file mode 100644 index 0000000000..dc0169b4e2 --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ProtocolSettings.java @@ -0,0 +1,215 @@ +/* + * 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.bootstrap; + +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.builders.ProtocolBuilder; + +import java.util.Map; + +/** + * The settings of {@link ProtocolConfig protcol} + * + * @see ProtocolBuilder + * @since 2.7.3 + */ +public class ProtocolSettings extends AbstractSettings { + + private final ProtocolBuilder builder; + + public ProtocolSettings(ProtocolBuilder builder, DubboBootstrap dubboBootstrap) { + super(dubboBootstrap); + this.builder = builder; + } + + public ProtocolSettings name(String name) { + builder.name(name); + return this; + } + + public ProtocolSettings host(String host) { + builder.host(host); + return this; + } + + public ProtocolSettings port(Integer port) { + builder.port(port); + return this; + } + + public ProtocolSettings contextpath(String contextpath) { + builder.contextpath(contextpath); + return this; + } + + @Deprecated + public ProtocolSettings path(String path) { + builder.path(path); + return this; + } + + public ProtocolSettings threadpool(String threadpool) { + builder.threadpool(threadpool); + return this; + } + + public ProtocolSettings corethreads(Integer corethreads) { + builder.corethreads(corethreads); + return this; + } + + public ProtocolSettings threads(Integer threads) { + builder.threads(threads); + return this; + } + + public ProtocolSettings iothreads(Integer iothreads) { + builder.iothreads(iothreads); + return this; + } + + public ProtocolSettings queues(Integer queues) { + builder.queues(queues); + return this; + } + + public ProtocolSettings accepts(Integer accepts) { + builder.accepts(accepts); + return this; + } + + public ProtocolSettings codec(String codec) { + builder.codec(codec); + return this; + } + + public ProtocolSettings serialization(String serialization) { + builder.serialization(serialization); + return this; + } + + public ProtocolSettings charset(String charset) { + builder.charset(charset); + return this; + } + + public ProtocolSettings payload(Integer payload) { + builder.payload(payload); + return this; + } + + public ProtocolSettings buffer(Integer buffer) { + builder.buffer(buffer); + return this; + } + + public ProtocolSettings heartbeat(Integer heartbeat) { + builder.heartbeat(heartbeat); + return this; + } + + public ProtocolSettings accesslog(String accesslog) { + builder.accesslog(accesslog); + return this; + } + + public ProtocolSettings transporter(String transporter) { + builder.transporter(transporter); + return this; + } + + public ProtocolSettings exchanger(String exchanger) { + builder.exchanger(exchanger); + return this; + } + + public ProtocolSettings dispatcher(String dispatcher) { + builder.dispatcher(dispatcher); + return this; + } + + @Deprecated + public ProtocolSettings dispather(String dispather) { + builder.dispather(dispather); + return this; + } + + public ProtocolSettings networker(String networker) { + builder.networker(networker); + return this; + } + + public ProtocolSettings server(String server) { + builder.server(server); + return this; + } + + public ProtocolSettings client(String client) { + builder.client(client); + return this; + } + + public ProtocolSettings telnet(String telnet) { + builder.telnet(telnet); + return this; + } + + public ProtocolSettings prompt(String prompt) { + builder.prompt(prompt); + return this; + } + + public ProtocolSettings status(String status) { + builder.status(status); + return this; + } + + public ProtocolSettings register(Boolean register) { + builder.register(register); + return this; + } + + public ProtocolSettings keepAlive(Boolean keepAlive) { + builder.keepAlive(keepAlive); + return this; + } + + public ProtocolSettings optimizer(String optimizer) { + builder.optimizer(optimizer); + return this; + } + + public ProtocolSettings extension(String extension) { + builder.extension(extension); + return this; + } + + public ProtocolSettings appendParameter(String key, String value) { + builder.appendParameter(key, value); + return this; + } + + public ProtocolSettings appendParameters(Map appendParameters) { + builder.appendParameters(appendParameters); + return this; + } + + public ProtocolSettings isDefault(Boolean isDefault) { + builder.isDefault(isDefault); + return this; + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ReferenceSettings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ReferenceSettings.java new file mode 100644 index 0000000000..b38ae05355 --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ReferenceSettings.java @@ -0,0 +1,334 @@ +/* + * 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.bootstrap; + +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ConfigCenterConfig; +import org.apache.dubbo.config.ConsumerConfig; +import org.apache.dubbo.config.MetadataReportConfig; +import org.apache.dubbo.config.MethodConfig; +import org.apache.dubbo.config.ModuleConfig; +import org.apache.dubbo.config.MonitorConfig; +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.builders.ReferenceBuilder; + +import java.util.List; +import java.util.Map; + +/** + * The settings of {@link ReferenceConfig} + * + * @since 2.7.3 + */ +public class ReferenceSettings extends AbstractSettings { + + private final ReferenceBuilder builder; + + public ReferenceSettings(ReferenceBuilder builder, DubboBootstrap dubboBootstrap) { + super(dubboBootstrap); + this.builder = builder; + } + + public ReferenceSettings interfaceName(String interfaceName) { + builder.interfaceName(interfaceName); + return this; + } + + public ReferenceSettings interfaceClass(Class interfaceClass) { + builder.interfaceClass(interfaceClass); + return this; + } + + public ReferenceSettings client(String client) { + builder.client(client); + return this; + } + + public ReferenceSettings url(String url) { + builder.url(url); + return this; + } + + public ReferenceSettings addMethods(List methods) { + builder.addMethods(methods); + return this; + } + + public ReferenceSettings addMethod(MethodConfig method) { + builder.addMethod(method); + return this; + } + + public ReferenceSettings consumer(ConsumerConfig consumer) { + builder.consumer(consumer); + return this; + } + + public ReferenceSettings protocol(String protocol) { + builder.protocol(protocol); + return this; + } + + public ReferenceSettings check(Boolean check) { + builder.check(check); + return this; + } + + public ReferenceSettings init(Boolean init) { + builder.init(init); + return this; + } + + public ReferenceSettings generic(String generic) { + builder.generic(generic); + return this; + } + + public ReferenceSettings generic(Boolean generic) { + builder.generic(generic); + return this; + } + + @Deprecated + public ReferenceSettings injvm(Boolean injvm) { + builder.injvm(injvm); + return this; + } + + public ReferenceSettings lazy(Boolean lazy) { + builder.lazy(lazy); + return this; + } + + public ReferenceSettings reconnect(String reconnect) { + builder.reconnect(reconnect); + return this; + } + + public ReferenceSettings sticky(Boolean sticky) { + builder.sticky(sticky); + return this; + } + + public ReferenceSettings version(String version) { + builder.version(version); + return this; + } + + public ReferenceSettings group(String group) { + builder.group(group); + return this; + } + + @Deprecated + public ReferenceSettings local(String local) { + builder.local(local); + return this; + } + + @Deprecated + public ReferenceSettings local(Boolean local) { + builder.local(local); + return this; + } + + public ReferenceSettings stub(String stub) { + builder.stub(stub); + return this; + } + + public ReferenceSettings stub(Boolean stub) { + builder.stub(stub); + return this; + } + + public ReferenceSettings monitor(MonitorConfig monitor) { + builder.monitor(monitor); + return this; + } + + public ReferenceSettings monitor(String monitor) { + builder.monitor(monitor); + return this; + } + + public ReferenceSettings proxy(String proxy) { + builder.proxy(proxy); + return this; + } + + public ReferenceSettings cluster(String cluster) { + builder.cluster(cluster); + return this; + } + + public ReferenceSettings filter(String filter) { + builder.filter(filter); + return this; + } + + public ReferenceSettings listener(String listener) { + builder.listener(listener); + return this; + } + + public ReferenceSettings owner(String owner) { + builder.owner(owner); + return this; + } + + public ReferenceSettings connections(Integer connections) { + builder.connections(connections); + return this; + } + + public ReferenceSettings layer(String layer) { + builder.layer(layer); + return this; + } + + public ReferenceSettings application(ApplicationConfig application) { + builder.application(application); + return this; + } + + public ReferenceSettings module(ModuleConfig module) { + builder.module(module); + return this; + } + + public ReferenceSettings addRegistries(List registries) { + builder.addRegistries(registries); + return this; + } + + public ReferenceSettings addRegistry(RegistryConfig registry) { + builder.addRegistry(registry); + return this; + } + + public ReferenceSettings registryIds(String registryIds) { + builder.registryIds(registryIds); + return this; + } + + public ReferenceSettings onconnect(String onconnect) { + builder.onconnect(onconnect); + return this; + } + + public ReferenceSettings ondisconnect(String ondisconnect) { + builder.ondisconnect(ondisconnect); + return this; + } + + public ReferenceSettings metadataReportConfig(MetadataReportConfig metadataReportConfig) { + builder.metadataReportConfig(metadataReportConfig); + return this; + } + + public ReferenceSettings configCenter(ConfigCenterConfig configCenter) { + builder.configCenter(configCenter); + return this; + } + + public ReferenceSettings callbacks(Integer callbacks) { + builder.callbacks(callbacks); + return this; + } + + public ReferenceSettings scope(String scope) { + builder.scope(scope); + return this; + } + + public ReferenceSettings tag(String tag) { + builder.tag(tag); + return this; + } + + public ReferenceSettings timeout(Integer timeout) { + builder.timeout(timeout); + return this; + } + + public ReferenceSettings retries(Integer retries) { + builder.retries(retries); + return this; + } + + public ReferenceSettings actives(Integer actives) { + builder.actives(actives); + return this; + } + + public ReferenceSettings loadbalance(String loadbalance) { + builder.loadbalance(loadbalance); + return this; + } + + public ReferenceSettings async(Boolean async) { + builder.async(async); + return this; + } + + public ReferenceSettings sent(Boolean sent) { + builder.sent(sent); + return this; + } + + public ReferenceSettings mock(String mock) { + builder.mock(mock); + return this; + } + + public ReferenceSettings mock(Boolean mock) { + builder.mock(mock); + return this; + } + + public ReferenceSettings merger(String merger) { + builder.merger(merger); + return this; + } + + public ReferenceSettings cache(String cache) { + builder.cache(cache); + return this; + } + + public ReferenceSettings validation(String validation) { + builder.validation(validation); + return this; + } + + public ReferenceSettings appendParameters(Map appendParameters) { + builder.appendParameters(appendParameters); + return this; + } + + public ReferenceSettings appendParameter(String key, String value) { + builder.appendParameter(key, value); + return this; + } + + public ReferenceSettings forks(Integer forks) { + builder.forks(forks); + return this; + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/RegistrySettings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/RegistrySettings.java new file mode 100644 index 0000000000..40331ae4db --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/RegistrySettings.java @@ -0,0 +1,164 @@ +/* + * 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.bootstrap; + +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.builders.RegistryBuilder; + +import java.util.Map; + +/** + * The settings of {@link RegistryConfig} + * + * @since 2.7.3 + */ +public class RegistrySettings extends AbstractSettings { + + private final RegistryBuilder builder; + + public RegistrySettings(RegistryBuilder builder, DubboBootstrap dubboBootstrap) { + super(dubboBootstrap); + this.builder = builder; + } + + public RegistrySettings address(String address) { + builder.address(address); + return this; + } + + public RegistrySettings username(String username) { + builder.username(username); + return this; + } + + public RegistrySettings password(String password) { + builder.password(password); + return this; + } + + public RegistrySettings port(Integer port) { + builder.port(port); + return this; + } + + public RegistrySettings protocol(String protocol) { + builder.protocol(protocol); + return this; + } + + public RegistrySettings transporter(String transporter) { + builder.transporter(transporter); + return this; + } + + @Deprecated + public RegistrySettings transport(String transport) { + builder.transport(transport); + return this; + } + + public RegistrySettings server(String server) { + builder.server(server); + return this; + } + + public RegistrySettings client(String client) { + builder.client(client); + return this; + } + + public RegistrySettings cluster(String cluster) { + builder.cluster(cluster); + return this; + } + + public RegistrySettings group(String group) { + builder.group(group); + return this; + } + + public RegistrySettings version(String version) { + builder.version(version); + return this; + } + + public RegistrySettings timeout(Integer timeout) { + builder.timeout(timeout); + return this; + } + + public RegistrySettings session(Integer session) { + builder.session(session); + return this; + } + + public RegistrySettings file(String file) { + builder.file(file); + return this; + } + + @Deprecated + public RegistrySettings wait(Integer wait) { + builder.wait(wait); + return this; + } + + public RegistrySettings isCheck(Boolean check) { + builder.isCheck(check); + return this; + } + + public RegistrySettings isDynamic(Boolean dynamic) { + builder.isDynamic(dynamic); + return this; + } + + public RegistrySettings register(Boolean register) { + builder.register(register); + return this; + } + + public RegistrySettings subscribe(Boolean subscribe) { + builder.subscribe(subscribe); + return this; + } + + public RegistrySettings appendParameter(String key, String value) { + builder.appendParameter(key, value); + return this; + } + + public RegistrySettings appendParameters(Map appendParameters) { + builder.appendParameters(appendParameters); + return this; + } + + public RegistrySettings isDefault(Boolean isDefault) { + builder.isDefault(isDefault); + return this; + } + + public RegistrySettings simplified(Boolean simplified) { + builder.simplified(simplified); + return this; + } + + public RegistrySettings extraKeys(String extraKeys) { + builder.extraKeys(extraKeys); + return this; + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ServiceSettings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ServiceSettings.java new file mode 100644 index 0000000000..314482e0e2 --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/ServiceSettings.java @@ -0,0 +1,384 @@ +/* + * 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.bootstrap; + +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ConfigCenterConfig; +import org.apache.dubbo.config.MetadataReportConfig; +import org.apache.dubbo.config.MethodConfig; +import org.apache.dubbo.config.ModuleConfig; +import org.apache.dubbo.config.MonitorConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.ProviderConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.builders.ServiceBuilder; + +import java.util.List; +import java.util.Map; + +/** + * The settings of {@link ServiceConfig Dubbo service} + * + * @since 2.7.3 + */ +public class ServiceSettings extends AbstractSettings { + + private final ServiceBuilder builder; + + public ServiceSettings(ServiceBuilder builder, DubboBootstrap dubboBootstrap) { + super(dubboBootstrap); + this.builder = builder; + } + + public ServiceSettings interfaceName(String interfaceName) { + builder.interfaceName(interfaceName); + return this; + } + + public ServiceSettings interfaceClass(Class interfaceClass) { + builder.interfaceClass(interfaceClass); + return this; + } + + public ServiceSettings ref(S ref) { + builder.ref(ref); + return this; + } + + public ServiceSettings path(String path) { + builder.path(path); + return this; + } + + public ServiceSettings addMethod(MethodConfig method) { + builder.addMethod(method); + return this; + } + + public ServiceSettings addMethods(List methods) { + builder.addMethods(methods); + return this; + } + + public ServiceSettings provider(ProviderConfig provider) { + builder.provider(provider); + return this; + } + + public ServiceSettings providerIds(String providerIds) { + builder.providerIds(providerIds); + return this; + } + + public ServiceSettings generic(String generic) { + builder.generic(generic); + return this; + } + + public ServiceSettings mock(String mock) { + builder.mock(mock); + return this; + } + + public ServiceSettings mock(Boolean mock) { + builder.mock(mock); + return this; + } + + public ServiceSettings version(String version) { + builder.version(version); + return this; + } + + public ServiceSettings group(String group) { + builder.group(group); + return this; + } + + public ServiceSettings deprecated(Boolean deprecated) { + builder.deprecated(deprecated); + return this; + } + + public ServiceSettings delay(Integer delay) { + builder.delay(delay); + return this; + } + + public ServiceSettings export(Boolean export) { + builder.export(export); + return this; + } + + public ServiceSettings weight(Integer weight) { + builder.weight(weight); + return this; + } + + public ServiceSettings document(String document) { + builder.document(document); + return this; + } + + public ServiceSettings dynamic(Boolean dynamic) { + builder.dynamic(dynamic); + return this; + } + + public ServiceSettings token(String token) { + builder.token(token); + return this; + } + + public ServiceSettings token(Boolean token) { + builder.token(token); + return this; + } + + public ServiceSettings accesslog(String accesslog) { + builder.accesslog(accesslog); + return this; + } + + public ServiceSettings accesslog(Boolean accesslog) { + builder.accesslog(accesslog); + return this; + } + + public ServiceSettings addProtocols(List protocols) { + builder.addProtocols(protocols); + return this; + } + + public ServiceSettings addProtocol(ProtocolConfig protocol) { + builder.addProtocol(protocol); + return this; + } + + public ServiceSettings protocolIds(String protocolIds) { + builder.protocolIds(protocolIds); + return this; + } + + public ServiceSettings executes(Integer executes) { + builder.executes(executes); + return this; + } + + public ServiceSettings register(Boolean register) { + builder.register(register); + return this; + } + + public ServiceSettings warmup(Integer warmup) { + builder.warmup(warmup); + return this; + } + + public ServiceSettings serialization(String serialization) { + builder.serialization(serialization); + return this; + } + + @Deprecated + public ServiceSettings local(String local) { + builder.local(local); + return this; + } + + @Deprecated + public ServiceSettings local(Boolean local) { + builder.local(local); + return this; + } + + public ServiceSettings stub(String stub) { + builder.stub(stub); + return this; + } + + public ServiceSettings stub(Boolean stub) { + builder.stub(stub); + return this; + } + + public ServiceSettings monitor(MonitorConfig monitor) { + builder.monitor(monitor); + return this; + } + + public ServiceSettings monitor(String monitor) { + builder.monitor(monitor); + return this; + } + + public ServiceSettings proxy(String proxy) { + builder.proxy(proxy); + return this; + } + + public ServiceSettings cluster(String cluster) { + builder.cluster(cluster); + return this; + } + + public ServiceSettings filter(String filter) { + builder.filter(filter); + return this; + } + + public ServiceSettings listener(String listener) { + builder.listener(listener); + return this; + } + + public ServiceSettings owner(String owner) { + builder.owner(owner); + return this; + } + + public ServiceSettings connections(Integer connections) { + builder.connections(connections); + return this; + } + + public ServiceSettings layer(String layer) { + builder.layer(layer); + return this; + } + + public ServiceSettings application(ApplicationConfig application) { + builder.application(application); + return this; + } + + public ServiceSettings module(ModuleConfig module) { + builder.module(module); + return this; + } + + public ServiceSettings addRegistries(List registries) { + builder.addRegistries(registries); + return this; + } + + public ServiceSettings addRegistry(RegistryConfig registry) { + builder.addRegistry(registry); + return this; + } + + public ServiceSettings registryIds(String registryIds) { + builder.registryIds(registryIds); + return this; + } + + public ServiceSettings onconnect(String onconnect) { + builder.onconnect(onconnect); + return this; + } + + public ServiceSettings ondisconnect(String ondisconnect) { + builder.ondisconnect(ondisconnect); + return this; + } + + public ServiceSettings metadataReportConfig(MetadataReportConfig metadataReportConfig) { + builder.metadataReportConfig(metadataReportConfig); + return this; + } + + public ServiceSettings configCenter(ConfigCenterConfig configCenter) { + builder.configCenter(configCenter); + return this; + } + + public ServiceSettings callbacks(Integer callbacks) { + builder.callbacks(callbacks); + return this; + } + + public ServiceSettings scope(String scope) { + builder.scope(scope); + return this; + } + + public ServiceSettings tag(String tag) { + builder.tag(tag); + return this; + } + + public ServiceSettings timeout(Integer timeout) { + builder.timeout(timeout); + return this; + } + + public ServiceSettings retries(Integer retries) { + builder.retries(retries); + return this; + } + + public ServiceSettings actives(Integer actives) { + builder.actives(actives); + return this; + } + + public ServiceSettings loadbalance(String loadbalance) { + builder.loadbalance(loadbalance); + return this; + } + + public ServiceSettings async(Boolean async) { + builder.async(async); + return this; + } + + public ServiceSettings sent(Boolean sent) { + builder.sent(sent); + return this; + } + + public ServiceSettings merger(String merger) { + builder.merger(merger); + return this; + } + + public ServiceSettings cache(String cache) { + builder.cache(cache); + return this; + } + + public ServiceSettings validation(String validation) { + builder.validation(validation); + return this; + } + + public ServiceSettings appendParameters(Map appendParameters) { + builder.appendParameters(appendParameters); + return this; + } + + public ServiceSettings appendParameter(String key, String value) { + builder.appendParameter(key, value); + return this; + } + + public ServiceSettings forks(Integer forks) { + builder.forks(forks); + return this; + } +} diff --git a/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/Settings.java b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/Settings.java new file mode 100644 index 0000000000..abf814184b --- /dev/null +++ b/dubbo-bootstrap/src/main/java/org/apache/dubbo/bootstrap/Settings.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.bootstrap; + +/** + * The Dubbo settings + * + * @since 2.7.3 + */ +public interface Settings { + + /** + * Go next settings + * + * @return {@link DubboBootstrap} + */ + DubboBootstrap next(); +} diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboBootstrapTest.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboBootstrapTest.java new file mode 100644 index 0000000000..b9acd2961f --- /dev/null +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboBootstrapTest.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.bootstrap; + +import org.apache.dubbo.common.utils.NetUtils; + +import org.apache.curator.test.TestingServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * {@link DubboBootstrap} Test + * + * @since 2.7.3 + */ +public class DubboBootstrapTest { + + private static int zkServerPort = NetUtils.getAvailablePort(); + + private static TestingServer zkServer; + + + @BeforeAll + public static void init() throws Exception { + zkServer = new TestingServer(zkServerPort, true); + } + + @AfterAll + public static void destroy() throws IOException { + zkServer.stop(); + zkServer.close(); + } + + @Test + public void testProviderInFluentAPI() { + + new DubboBootstrap() + .application("dubbo-provider-demo") + .next() + .registry() + .address("zookeeper://127.0.0.1:" + zkServerPort + "?registry-type=service") + .next() + .protocol() + .name("dubbo") + .port(-1) + .next() + .service("test") + .interfaceClass(EchoService.class) + .ref(new EchoServiceImpl()) + .group("DEFAULT") + .version("1.0.0") + .next() + .start() + .stop(); + + } + + @Test + public void testProviderInLambda() { + new DubboBootstrap() + .application("dubbo-provider-demo", builder -> { + }) + .registry("default", builder -> + builder.address("zookeeper://127.0.0.1:" + zkServerPort + "?registry-type=service") + ) + .protocol("defalt", builder -> + builder.name("dubbo") + .port(-1) + ) + .service("test", builder -> + builder.interfaceClass(EchoService.class) + .ref(new EchoServiceImpl()) + .group("DEFAULT") + .version("1.0.0") + ) + .start() + .stop(); + } +} diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java new file mode 100644 index 0000000000..800f29de6e --- /dev/null +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceConsumerBootstrap.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.bootstrap; + +import org.apache.dubbo.config.ReferenceConfig; + +import static org.apache.dubbo.bootstrap.EchoService.GROUP; +import static org.apache.dubbo.bootstrap.EchoService.VERSION; + +/** + * Dubbo Provider Bootstrap + * + * @since 2.7.3 + */ +public class DubboServiceConsumerBootstrap { + + public static void main(String[] args) throws Exception { + + DubboBootstrap bootstrap = new DubboBootstrap() + .application("dubbo-consumer-demo") + .next() + .registry() + .address("nacos://127.0.0.1:8848?registry-type=service&subscribed-services=dubbo-provider-demo") + .next() + .reference("ref") + .interfaceClass(EchoService.class) + .group(GROUP) + .version(VERSION) + .next() + .onlyRegisterProvider(true) + .start() + .await(); + + ReferenceConfig referenceConfig = bootstrap.referenceConfig("ref"); + + EchoService echoService = referenceConfig.get(); + + for (int i = 0; i < 500; i++) { + Thread.sleep(2000L); + System.out.println(echoService.echo("Hello,World")); + } + + } +} diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java new file mode 100644 index 0000000000..bb149ad7a5 --- /dev/null +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/DubboServiceProviderBootstrap.java @@ -0,0 +1,52 @@ +/* + * 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.bootstrap; + +import java.io.IOException; + +import static org.apache.dubbo.bootstrap.EchoService.GROUP; +import static org.apache.dubbo.bootstrap.EchoService.VERSION; + +/** + * Dubbo Provider Bootstrap + * + * @since 2.7.3 + */ +public class DubboServiceProviderBootstrap { + + public static void main(String[] args) throws IOException { + + new DubboBootstrap() + .application("dubbo-provider-demo") + .next() + .registry() + .address("nacos://127.0.0.1:8848?registry-type=service") + .next() + .protocol() + .name("dubbo") + .port(-1) + .next() + .service("test") + .interfaceClass(EchoService.class) + .ref(new EchoServiceImpl()) + .group(GROUP) + .version(VERSION) + .next() + .start() + .await(); + } +} diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoService.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoService.java new file mode 100644 index 0000000000..7ad8698452 --- /dev/null +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoService.java @@ -0,0 +1,31 @@ +/* + * 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.bootstrap; + +/** + * Echo Service + * + * @since 2.7.3 + */ +public interface EchoService { + + String GROUP = "DEFAULT"; + + String VERSION = "1.0.0"; + + String echo(String message); +} diff --git a/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoServiceImpl.java b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoServiceImpl.java new file mode 100644 index 0000000000..e0218b1e22 --- /dev/null +++ b/dubbo-bootstrap/src/test/java/org/apache/dubbo/bootstrap/EchoServiceImpl.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.bootstrap; + +import org.apache.dubbo.rpc.RpcContext; + +import static java.lang.String.format; + +/** + * The implementation of {@link EchoService} + * + * @see EchoService + * @since 2.7.3 + */ +public class EchoServiceImpl implements EchoService { + + @Override + public String echo(String message) { + RpcContext rpcContext = RpcContext.getContext(); + return format("[%s:%s] ECHO - %s", rpcContext.getLocalHost(), rpcContext.getLocalPort(), message); + } +} diff --git a/dubbo-bootstrap/src/test/resources/log4j.properties b/dubbo-bootstrap/src/test/resources/log4j.properties new file mode 100644 index 0000000000..15a0900f0d --- /dev/null +++ b/dubbo-bootstrap/src/test/resources/log4j.properties @@ -0,0 +1,7 @@ +###set log levels### +log4j.rootLogger=info, stdout +###output to the console### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy HH:mm:ss:SSS z}] %t %5p %c{2}: %m%n \ No newline at end of file diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionRouter.java index 87e9b25a00..3ac978d89c 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionRouter.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/ConditionRouter.java @@ -38,16 +38,16 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX; +import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY; import static org.apache.dubbo.rpc.cluster.Constants.ADDRESS_KEY; import static org.apache.dubbo.rpc.cluster.Constants.FORCE_KEY; import static org.apache.dubbo.rpc.cluster.Constants.PRIORITY_KEY; import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY; import static org.apache.dubbo.rpc.cluster.Constants.RUNTIME_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX; -import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.METHOD_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; /** * ConditionRouter diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ClusterUtils.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ClusterUtils.java index 7a2894b7b1..8eca3c11e9 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ClusterUtils.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/ClusterUtils.java @@ -23,7 +23,6 @@ import org.apache.dubbo.remoting.Constants; import java.util.HashMap; import java.util.Map; -import static org.apache.dubbo.rpc.cluster.Constants.TAG_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ALIVE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; import static org.apache.dubbo.common.constants.CommonConstants.CORE_THREADS_KEY; @@ -41,6 +40,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; import static org.apache.dubbo.rpc.Constants.INVOKER_LISTENER_KEY; import static org.apache.dubbo.rpc.Constants.REFERENCE_FILTER_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.TAG_KEY; /** * ClusterUtils diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java index 871e075bbe..1a67587fe8 100644 --- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/ClusterUtilsTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import static org.apache.dubbo.common.constants.CommonConstants.ALIVE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.CORE_THREADS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY; import static org.apache.dubbo.common.constants.CommonConstants.QUEUES_KEY; @@ -32,7 +33,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.THREAD_NAME_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; public class ClusterUtilsTest { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java index 106bebc70c..82627f228c 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java @@ -40,20 +40,20 @@ import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; -import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PASSWORD_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.USERNAME_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; /** * URL - Uniform Resource Locator (Immutable, ThreadSafe) @@ -339,6 +339,30 @@ class URL implements Serializable { } } + static String appendDefaultPort(String address, int defaultPort) { + if (address != null && address.length() > 0 && defaultPort > 0) { + int i = address.indexOf(':'); + if (i < 0) { + return address + ":" + defaultPort; + } else if (Integer.parseInt(address.substring(i + 1)) == 0) { + return address.substring(0, i + 1) + defaultPort; + } + } + return address; + } + + public static String buildKey(String path, String group, String version) { + StringBuilder buf = new StringBuilder(); + if (group != null && group.length() > 0) { + buf.append(group).append("/"); + } + buf.append(path); + if (version != null && version.length() > 0) { + buf.append(":").append(version); + } + return buf.toString(); + } + public String getProtocol() { return protocol; } @@ -452,18 +476,6 @@ class URL implements Serializable { return urls; } - static String appendDefaultPort(String address, int defaultPort) { - if (address != null && address.length() > 0 && defaultPort > 0) { - int i = address.indexOf(':'); - if (i < 0) { - return address + ":" + defaultPort; - } else if (Integer.parseInt(address.substring(i + 1)) == 0) { - return address.substring(0, i + 1) + defaultPort; - } - } - return address; - } - public String getPath() { return path; } @@ -1325,6 +1337,7 @@ class URL implements Serializable { /** * The format of return value is '{group}/{interfaceName}:{version}' + * * @return */ public String getServiceKey() { @@ -1337,6 +1350,7 @@ class URL implements Serializable { /** * The format of return value is '{group}/{path/interfaceName}:{version}' + * * @return */ public String getPathKey() { @@ -1347,18 +1361,6 @@ class URL implements Serializable { return buildKey(inf, getParameter(GROUP_KEY), getParameter(VERSION_KEY)); } - public static String buildKey(String path, String group, String version) { - StringBuilder buf = new StringBuilder(); - if (group != null && group.length() > 0) { - buf.append(group).append("/"); - } - buf.append(path); - if (version != null && version.length() > 0) { - buf.append(":").append(version); - } - return buf.toString(); - } - public String toServiceStringWithoutResolving() { return buildString(true, false, false, true); } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java index 647ac81c7b..b28fe39ce0 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/RegistryConstants.java @@ -52,4 +52,38 @@ public interface RegistryConstants { String OVERRIDE_PROTOCOL = "override"; String COMPATIBLE_CONFIG_KEY = "compatible_config"; + + /** + * The parameter key of Dubbo Registry type + * + * @since 2.7.3 + */ + String REGISTRY_TYPE_KEY = "registry-type"; + + /** + * The parameter value of Service-Oriented Registry type + * + * @since 2.7.3 + */ + String SERVICE_REGISTRY_TYPE = "service"; + + /** + * The parameter key of the subscribed service names for Service-Oriented Registry + * + * @since 2.7.3 + */ + String SUBSCRIBED_SERVICE_NAMES_KEY = "subscribed-services"; + + + /** + * The request size of service instances + * + * @since 2.7.3 + */ + String INSTANCES_REQUEST_SIZE_KEY = "instances-request-size"; + + /** + * The default request size of service instances + */ + int DEFAULT_INSTANCES_REQUEST_SIZE = 100; } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableConsumer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableConsumer.java new file mode 100644 index 0000000000..28561bb060 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableConsumer.java @@ -0,0 +1,66 @@ +/* + * 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.function; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * {@link Consumer} with {@link Throwable} + * + * @param the source type + * @see Function + * @see Throwable + * @since 2.7.3 + */ +@FunctionalInterface +public interface ThrowableConsumer { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @throws Throwable if met with any error + */ + void accept(T t) throws Throwable; + + /** + * Executes {@link ThrowableConsumer} + * + * @param t the function argument + * @throws RuntimeException wrappers {@link Throwable} + */ + default void execute(T t) throws RuntimeException { + try { + accept(t); + } catch (Throwable e) { + throw new RuntimeException(e.getMessage(), e.getCause()); + } + } + + /** + * Executes {@link ThrowableConsumer} + * + * @param t the function argument + * @param consumer {@link ThrowableConsumer} + * @param the source type + * @return the result after execution + */ + static void execute(T t, ThrowableConsumer consumer) { + consumer.execute(t); + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java b/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java new file mode 100644 index 0000000000..eb7171d9e2 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java @@ -0,0 +1,71 @@ +/* + * 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.function; + +import java.util.function.Function; + +/** + * {@link Function} with {@link Throwable} + * + * @param the source type + * @param the return type + * @see Function + * @see Throwable + * @since 2.7.3 + */ +@FunctionalInterface +public interface ThrowableFunction { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + * @throws Throwable if met with any error + */ + R apply(T t) throws Throwable; + + /** + * Executes {@link ThrowableFunction} + * + * @param t the function argument + * @return the function result + * @throws RuntimeException wrappers {@link Throwable} + */ + default R execute(T t) throws RuntimeException { + R result = null; + try { + result = apply(t); + } catch (Throwable e) { + throw new RuntimeException(e.getCause()); + } + return result; + } + + /** + * Executes {@link ThrowableFunction} + * + * @param t the function argument + * @param function {@link ThrowableFunction} + * @param the source type + * @param the return type + * @return the result after execution + */ + static R execute(T t, ThrowableFunction function) { + return function.execute(t); + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/DefaultPage.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/DefaultPage.java new file mode 100644 index 0000000000..fd408b57dd --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/DefaultPage.java @@ -0,0 +1,82 @@ +/* + * 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.utils; + +import java.io.Serializable; +import java.util.List; + +/** + * The default implementation of {@link Page} + * + * @since 2.7.3 + */ +public class DefaultPage implements Page, Serializable { + + private static final long serialVersionUID = 1099331838954070419L; + + private final int requestOffset; + + private final int pageSize; + + private final int totalSize; + + private final List data; + + private final int totalPages; + + private final boolean hasNext; + + public DefaultPage(int requestOffset, int pageSize, List data, int totalSize) { + this.requestOffset = requestOffset; + this.pageSize = pageSize; + this.data = data; + this.totalSize = totalSize; + int remain = totalSize % pageSize; + this.totalPages = remain > 0 ? (totalSize / pageSize) + 1 : totalSize / pageSize; + this.hasNext = totalSize - requestOffset - pageSize > 0; + } + + @Override + public int getOffset() { + return requestOffset; + } + + @Override + public int getPageSize() { + return pageSize; + } + + @Override + public int getTotalSize() { + return totalSize; + } + + @Override + public int getTotalPages() { + return totalPages; + } + + @Override + public List getData() { + return data; + } + + @Override + public boolean hasNext() { + return hasNext; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java index 6fd1a4e27e..c2fea8b938 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java @@ -37,9 +37,9 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND; import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_KEY; import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND; /** * IP and Port Helper for RPC diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Page.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Page.java new file mode 100644 index 0000000000..c15cfb84fd --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Page.java @@ -0,0 +1,87 @@ +/* + * 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.utils; + +import java.util.List; + +/** + * The model class of pagination + * + * @since 2.7.3 + */ +public interface Page { + + /** + * Gets the offset of request + * + * @return positive integer + */ + int getOffset(); + + /** + * Gets the size of request for pagination query + * + * @return positive integer + */ + int getPageSize(); + + /** + * Gets the total amount of elements. + * + * @return the total amount of elements + */ + int getTotalSize(); + + /** + * Get the number of total pages. + * + * @return the number of total pages. + */ + int getTotalPages(); + + /** + * The data of current page + * + * @return non-null {@link List} + */ + List getData(); + + /** + * The size of {@link #getData() data} + * + * @return positive integer + */ + default int getDataSize() { + return getData().size(); + } + + /** + * It indicates has next page or not + * + * @return if has , return true, or false + */ + boolean hasNext(); + + /** + * Returns whether the page has data at all. + * + * @return + */ + default boolean hasData() { + return getDataSize() > 0; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java index 0c1a13dfe2..437f1318cd 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java @@ -1,1122 +1,1187 @@ -/* - * 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.utils; - -import javassist.CtClass; -import javassist.CtConstructor; -import javassist.CtMethod; -import javassist.NotFoundException; - -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.net.URL; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Future; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * ReflectUtils - */ -public final class ReflectUtils { - - /** - * void(V). - */ - public static final char JVM_VOID = 'V'; - - /** - * boolean(Z). - */ - public static final char JVM_BOOLEAN = 'Z'; - - /** - * byte(B). - */ - public static final char JVM_BYTE = 'B'; - - /** - * char(C). - */ - public static final char JVM_CHAR = 'C'; - - /** - * double(D). - */ - public static final char JVM_DOUBLE = 'D'; - - /** - * float(F). - */ - public static final char JVM_FLOAT = 'F'; - - /** - * int(I). - */ - public static final char JVM_INT = 'I'; - - /** - * long(J). - */ - public static final char JVM_LONG = 'J'; - - /** - * short(S). - */ - public static final char JVM_SHORT = 'S'; - - public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - - public static final String JAVA_IDENT_REGEX = "(?:[_$a-zA-Z][_$a-zA-Z0-9]*)"; - - public static final String JAVA_NAME_REGEX = "(?:" + JAVA_IDENT_REGEX + "(?:\\." + JAVA_IDENT_REGEX + ")*)"; - - public static final String CLASS_DESC = "(?:L" + JAVA_IDENT_REGEX + "(?:\\/" + JAVA_IDENT_REGEX + ")*;)"; - - public static final String ARRAY_DESC = "(?:\\[+(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "))"; - - public static final String DESC_REGEX = "(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "|" + ARRAY_DESC + ")"; - - public static final Pattern DESC_PATTERN = Pattern.compile(DESC_REGEX); - - public static final String METHOD_DESC_REGEX = "(?:(" + JAVA_IDENT_REGEX + ")?\\((" + DESC_REGEX + "*)\\)(" + DESC_REGEX + ")?)"; - - public static final Pattern METHOD_DESC_PATTERN = Pattern.compile(METHOD_DESC_REGEX); - - public static final Pattern GETTER_METHOD_DESC_PATTERN = Pattern.compile("get([A-Z][_a-zA-Z0-9]*)\\(\\)(" + DESC_REGEX + ")"); - - public static final Pattern SETTER_METHOD_DESC_PATTERN = Pattern.compile("set([A-Z][_a-zA-Z0-9]*)\\((" + DESC_REGEX + ")\\)V"); - - public static final Pattern IS_HAS_CAN_METHOD_DESC_PATTERN = Pattern.compile("(?:is|has|can)([A-Z][_a-zA-Z0-9]*)\\(\\)Z"); - - private static final ConcurrentMap> DESC_CLASS_CACHE = new ConcurrentHashMap>(); - - private static final ConcurrentMap> NAME_CLASS_CACHE = new ConcurrentHashMap>(); - - private static final ConcurrentMap Signature_METHODS_CACHE = new ConcurrentHashMap(); - - private ReflectUtils() { - } - - public static boolean isPrimitives(Class cls) { - if (cls.isArray()) { - return isPrimitive(cls.getComponentType()); - } - return isPrimitive(cls); - } - - public static boolean isPrimitive(Class cls) { - return cls.isPrimitive() || cls == String.class || cls == Boolean.class || cls == Character.class - || Number.class.isAssignableFrom(cls) || Date.class.isAssignableFrom(cls); - } - - public static Class getBoxedClass(Class c) { - if (c == int.class) { - c = Integer.class; - } else if (c == boolean.class) { - c = Boolean.class; - } else if (c == long.class) { - c = Long.class; - } else if (c == float.class) { - c = Float.class; - } else if (c == double.class) { - c = Double.class; - } else if (c == char.class) { - c = Character.class; - } else if (c == byte.class) { - c = Byte.class; - } else if (c == short.class) { - c = Short.class; - } - return c; - } - - /** - * is compatible. - * - * @param c class. - * @param o instance. - * @return compatible or not. - */ - public static boolean isCompatible(Class c, Object o) { - boolean pt = c.isPrimitive(); - if (o == null) { - return !pt; - } - - if (pt) { - c = getBoxedClass(c); - } - - return c == o.getClass() || c.isInstance(o); - } - - /** - * is compatible. - * - * @param cs class array. - * @param os object array. - * @return compatible or not. - */ - public static boolean isCompatible(Class[] cs, Object[] os) { - int len = cs.length; - if (len != os.length) { - return false; - } - if (len == 0) { - return true; - } - for (int i = 0; i < len; i++) { - if (!isCompatible(cs[i], os[i])) { - return false; - } - } - return true; - } - - public static String getCodeBase(Class cls) { - if (cls == null) { - return null; - } - ProtectionDomain domain = cls.getProtectionDomain(); - if (domain == null) { - return null; - } - CodeSource source = domain.getCodeSource(); - if (source == null) { - return null; - } - URL location = source.getLocation(); - if (location == null) { - return null; - } - return location.getFile(); - } - - /** - * get name. - * java.lang.Object[][].class => "java.lang.Object[][]" - * - * @param c class. - * @return name. - */ - public static String getName(Class c) { - if (c.isArray()) { - StringBuilder sb = new StringBuilder(); - do { - sb.append("[]"); - c = c.getComponentType(); - } - while (c.isArray()); - - return c.getName() + sb.toString(); - } - return c.getName(); - } - - public static Class getGenericClass(Class cls) { - return getGenericClass(cls, 0); - } - - public static Class getGenericClass(Class cls, int i) { - try { - ParameterizedType parameterizedType = ((ParameterizedType) cls.getGenericInterfaces()[0]); - Object genericClass = parameterizedType.getActualTypeArguments()[i]; - if (genericClass instanceof ParameterizedType) { // handle nested generic type - return (Class) ((ParameterizedType) genericClass).getRawType(); - } else if (genericClass instanceof GenericArrayType) { // handle array generic type - return (Class) ((GenericArrayType) genericClass).getGenericComponentType(); - } else if (((Class) genericClass).isArray()) { - // Requires JDK 7 or higher, Foo is no longer GenericArrayType - return ((Class) genericClass).getComponentType(); - } else { - return (Class) genericClass; - } - } catch (Throwable e) { - throw new IllegalArgumentException(cls.getName() - + " generic type undefined!", e); - } - } - - /** - * get method name. - * "void do(int)", "void do()", "int do(java.lang.String,boolean)" - * - * @param m method. - * @return name. - */ - public static String getName(final Method m) { - StringBuilder ret = new StringBuilder(); - ret.append(getName(m.getReturnType())).append(' '); - ret.append(m.getName()).append('('); - Class[] parameterTypes = m.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - if (i > 0) { - ret.append(','); - } - ret.append(getName(parameterTypes[i])); - } - ret.append(')'); - return ret.toString(); - } - - public static String getSignature(String methodName, Class[] parameterTypes) { - StringBuilder sb = new StringBuilder(methodName); - sb.append("("); - if (parameterTypes != null && parameterTypes.length > 0) { - boolean first = true; - for (Class type : parameterTypes) { - if (first) { - first = false; - } else { - sb.append(","); - } - sb.append(type.getName()); - } - } - sb.append(")"); - return sb.toString(); - } - - /** - * get constructor name. - * "()", "(java.lang.String,int)" - * - * @param c constructor. - * @return name. - */ - public static String getName(final Constructor c) { - StringBuilder ret = new StringBuilder("("); - Class[] parameterTypes = c.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - if (i > 0) { - ret.append(','); - } - ret.append(getName(parameterTypes[i])); - } - ret.append(')'); - return ret.toString(); - } - - /** - * get class desc. - * boolean[].class => "[Z" - * Object.class => "Ljava/lang/Object;" - * - * @param c class. - * @return desc. - * @throws NotFoundException - */ - public static String getDesc(Class c) { - StringBuilder ret = new StringBuilder(); - - while (c.isArray()) { - ret.append('['); - c = c.getComponentType(); - } - - if (c.isPrimitive()) { - String t = c.getName(); - if ("void".equals(t)) { - ret.append(JVM_VOID); - } else if ("boolean".equals(t)) { - ret.append(JVM_BOOLEAN); - } else if ("byte".equals(t)) { - ret.append(JVM_BYTE); - } else if ("char".equals(t)) { - ret.append(JVM_CHAR); - } else if ("double".equals(t)) { - ret.append(JVM_DOUBLE); - } else if ("float".equals(t)) { - ret.append(JVM_FLOAT); - } else if ("int".equals(t)) { - ret.append(JVM_INT); - } else if ("long".equals(t)) { - ret.append(JVM_LONG); - } else if ("short".equals(t)) { - ret.append(JVM_SHORT); - } - } else { - ret.append('L'); - ret.append(c.getName().replace('.', '/')); - ret.append(';'); - } - return ret.toString(); - } - - /** - * get class array desc. - * [int.class, boolean[].class, Object.class] => "I[ZLjava/lang/Object;" - * - * @param cs class array. - * @return desc. - * @throws NotFoundException - */ - public static String getDesc(final Class[] cs) { - if (cs.length == 0) { - return ""; - } - - StringBuilder sb = new StringBuilder(64); - for (Class c : cs) { - sb.append(getDesc(c)); - } - return sb.toString(); - } - - /** - * get method desc. - * int do(int arg1) => "do(I)I" - * void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V" - * - * @param m method. - * @return desc. - */ - public static String getDesc(final Method m) { - StringBuilder ret = new StringBuilder(m.getName()).append('('); - Class[] parameterTypes = m.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - ret.append(getDesc(parameterTypes[i])); - } - ret.append(')').append(getDesc(m.getReturnType())); - return ret.toString(); - } - - /** - * get constructor desc. - * "()V", "(Ljava/lang/String;I)V" - * - * @param c constructor. - * @return desc - */ - public static String getDesc(final Constructor c) { - StringBuilder ret = new StringBuilder("("); - Class[] parameterTypes = c.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - ret.append(getDesc(parameterTypes[i])); - } - ret.append(')').append('V'); - return ret.toString(); - } - - /** - * get method desc. - * "(I)I", "()V", "(Ljava/lang/String;Z)V" - * - * @param m method. - * @return desc. - */ - public static String getDescWithoutMethodName(Method m) { - StringBuilder ret = new StringBuilder(); - ret.append('('); - Class[] parameterTypes = m.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - ret.append(getDesc(parameterTypes[i])); - } - ret.append(')').append(getDesc(m.getReturnType())); - return ret.toString(); - } - - /** - * get class desc. - * Object.class => "Ljava/lang/Object;" - * boolean[].class => "[Z" - * - * @param c class. - * @return desc. - * @throws NotFoundException - */ - public static String getDesc(final CtClass c) throws NotFoundException { - StringBuilder ret = new StringBuilder(); - if (c.isArray()) { - ret.append('['); - ret.append(getDesc(c.getComponentType())); - } else if (c.isPrimitive()) { - String t = c.getName(); - if ("void".equals(t)) { - ret.append(JVM_VOID); - } else if ("boolean".equals(t)) { - ret.append(JVM_BOOLEAN); - } else if ("byte".equals(t)) { - ret.append(JVM_BYTE); - } else if ("char".equals(t)) { - ret.append(JVM_CHAR); - } else if ("double".equals(t)) { - ret.append(JVM_DOUBLE); - } else if ("float".equals(t)) { - ret.append(JVM_FLOAT); - } else if ("int".equals(t)) { - ret.append(JVM_INT); - } else if ("long".equals(t)) { - ret.append(JVM_LONG); - } else if ("short".equals(t)) { - ret.append(JVM_SHORT); - } - } else { - ret.append('L'); - ret.append(c.getName().replace('.', '/')); - ret.append(';'); - } - return ret.toString(); - } - - /** - * get method desc. - * "do(I)I", "do()V", "do(Ljava/lang/String;Z)V" - * - * @param m method. - * @return desc. - */ - public static String getDesc(final CtMethod m) throws NotFoundException { - StringBuilder ret = new StringBuilder(m.getName()).append('('); - CtClass[] parameterTypes = m.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - ret.append(getDesc(parameterTypes[i])); - } - ret.append(')').append(getDesc(m.getReturnType())); - return ret.toString(); - } - - /** - * get constructor desc. - * "()V", "(Ljava/lang/String;I)V" - * - * @param c constructor. - * @return desc - */ - public static String getDesc(final CtConstructor c) throws NotFoundException { - StringBuilder ret = new StringBuilder("("); - CtClass[] parameterTypes = c.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - ret.append(getDesc(parameterTypes[i])); - } - ret.append(')').append('V'); - return ret.toString(); - } - - /** - * get method desc. - * "(I)I", "()V", "(Ljava/lang/String;Z)V". - * - * @param m method. - * @return desc. - */ - public static String getDescWithoutMethodName(final CtMethod m) throws NotFoundException { - StringBuilder ret = new StringBuilder(); - ret.append('('); - CtClass[] parameterTypes = m.getParameterTypes(); - for (int i = 0; i < parameterTypes.length; i++) { - ret.append(getDesc(parameterTypes[i])); - } - ret.append(')').append(getDesc(m.getReturnType())); - return ret.toString(); - } - - /** - * name to desc. - * java.util.Map[][] => "[[Ljava/util/Map;" - * - * @param name name. - * @return desc. - */ - public static String name2desc(String name) { - StringBuilder sb = new StringBuilder(); - int c = 0, index = name.indexOf('['); - if (index > 0) { - c = (name.length() - index) / 2; - name = name.substring(0, index); - } - while (c-- > 0) { - sb.append("["); - } - if ("void".equals(name)) { - sb.append(JVM_VOID); - } else if ("boolean".equals(name)) { - sb.append(JVM_BOOLEAN); - } else if ("byte".equals(name)) { - sb.append(JVM_BYTE); - } else if ("char".equals(name)) { - sb.append(JVM_CHAR); - } else if ("double".equals(name)) { - sb.append(JVM_DOUBLE); - } else if ("float".equals(name)) { - sb.append(JVM_FLOAT); - } else if ("int".equals(name)) { - sb.append(JVM_INT); - } else if ("long".equals(name)) { - sb.append(JVM_LONG); - } else if ("short".equals(name)) { - sb.append(JVM_SHORT); - } else { - sb.append('L').append(name.replace('.', '/')).append(';'); - } - return sb.toString(); - } - - /** - * desc to name. - * "[[I" => "int[][]" - * - * @param desc desc. - * @return name. - */ - public static String desc2name(String desc) { - StringBuilder sb = new StringBuilder(); - int c = desc.lastIndexOf('[') + 1; - if (desc.length() == c + 1) { - switch (desc.charAt(c)) { - case JVM_VOID: { - sb.append("void"); - break; - } - case JVM_BOOLEAN: { - sb.append("boolean"); - break; - } - case JVM_BYTE: { - sb.append("byte"); - break; - } - case JVM_CHAR: { - sb.append("char"); - break; - } - case JVM_DOUBLE: { - sb.append("double"); - break; - } - case JVM_FLOAT: { - sb.append("float"); - break; - } - case JVM_INT: { - sb.append("int"); - break; - } - case JVM_LONG: { - sb.append("long"); - break; - } - case JVM_SHORT: { - sb.append("short"); - break; - } - default: - throw new RuntimeException(); - } - } else { - sb.append(desc.substring(c + 1, desc.length() - 1).replace('/', '.')); - } - while (c-- > 0) { - sb.append("[]"); - } - return sb.toString(); - } - - public static Class forName(String name) { - try { - return name2class(name); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Not found class " + name + ", cause: " + e.getMessage(), e); - } - } - - public static Class forName(ClassLoader cl, String name) { - try { - return name2class(cl, name); - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Not found class " + name + ", cause: " + e.getMessage(), e); - } - } - - /** - * name to class. - * "boolean" => boolean.class - * "java.util.Map[][]" => java.util.Map[][].class - * - * @param name name. - * @return Class instance. - */ - public static Class name2class(String name) throws ClassNotFoundException { - return name2class(ClassUtils.getClassLoader(), name); - } - - /** - * name to class. - * "boolean" => boolean.class - * "java.util.Map[][]" => java.util.Map[][].class - * - * @param cl ClassLoader instance. - * @param name name. - * @return Class instance. - */ - private static Class name2class(ClassLoader cl, String name) throws ClassNotFoundException { - int c = 0, index = name.indexOf('['); - if (index > 0) { - c = (name.length() - index) / 2; - name = name.substring(0, index); - } - if (c > 0) { - StringBuilder sb = new StringBuilder(); - while (c-- > 0) { - sb.append("["); - } - - if ("void".equals(name)) { - sb.append(JVM_VOID); - } else if ("boolean".equals(name)) { - sb.append(JVM_BOOLEAN); - } else if ("byte".equals(name)) { - sb.append(JVM_BYTE); - } else if ("char".equals(name)) { - sb.append(JVM_CHAR); - } else if ("double".equals(name)) { - sb.append(JVM_DOUBLE); - } else if ("float".equals(name)) { - sb.append(JVM_FLOAT); - } else if ("int".equals(name)) { - sb.append(JVM_INT); - } else if ("long".equals(name)) { - sb.append(JVM_LONG); - } else if ("short".equals(name)) { - sb.append(JVM_SHORT); - } else { - sb.append('L').append(name).append(';'); // "java.lang.Object" ==> "Ljava.lang.Object;" - } - name = sb.toString(); - } else { - if ("void".equals(name)) { - return void.class; - } else if ("boolean".equals(name)) { - return boolean.class; - } else if ("byte".equals(name)) { - return byte.class; - } else if ("char".equals(name)) { - return char.class; - } else if ("double".equals(name)) { - return double.class; - } else if ("float".equals(name)) { - return float.class; - } else if ("int".equals(name)) { - return int.class; - } else if ("long".equals(name)) { - return long.class; - } else if ("short".equals(name)) { - return short.class; - } - } - - if (cl == null) { - cl = ClassUtils.getClassLoader(); - } - Class clazz = NAME_CLASS_CACHE.get(name); - if (clazz == null) { - clazz = Class.forName(name, true, cl); - NAME_CLASS_CACHE.put(name, clazz); - } - return clazz; - } - - /** - * desc to class. - * "[Z" => boolean[].class - * "[[Ljava/util/Map;" => java.util.Map[][].class - * - * @param desc desc. - * @return Class instance. - * @throws ClassNotFoundException - */ - public static Class desc2class(String desc) throws ClassNotFoundException { - return desc2class(ClassUtils.getClassLoader(), desc); - } - - /** - * desc to class. - * "[Z" => boolean[].class - * "[[Ljava/util/Map;" => java.util.Map[][].class - * - * @param cl ClassLoader instance. - * @param desc desc. - * @return Class instance. - * @throws ClassNotFoundException - */ - private static Class desc2class(ClassLoader cl, String desc) throws ClassNotFoundException { - switch (desc.charAt(0)) { - case JVM_VOID: - return void.class; - case JVM_BOOLEAN: - return boolean.class; - case JVM_BYTE: - return byte.class; - case JVM_CHAR: - return char.class; - case JVM_DOUBLE: - return double.class; - case JVM_FLOAT: - return float.class; - case JVM_INT: - return int.class; - case JVM_LONG: - return long.class; - case JVM_SHORT: - return short.class; - case 'L': - desc = desc.substring(1, desc.length() - 1).replace('/', '.'); // "Ljava/lang/Object;" ==> "java.lang.Object" - break; - case '[': - desc = desc.replace('/', '.'); // "[[Ljava/lang/Object;" ==> "[[Ljava.lang.Object;" - break; - default: - throw new ClassNotFoundException("Class not found: " + desc); - } - - if (cl == null) { - cl = ClassUtils.getClassLoader(); - } - Class clazz = DESC_CLASS_CACHE.get(desc); - if (clazz == null) { - clazz = Class.forName(desc, true, cl); - DESC_CLASS_CACHE.put(desc, clazz); - } - return clazz; - } - - /** - * get class array instance. - * - * @param desc desc. - * @return Class class array. - * @throws ClassNotFoundException - */ - public static Class[] desc2classArray(String desc) throws ClassNotFoundException { - Class[] ret = desc2classArray(ClassUtils.getClassLoader(), desc); - return ret; - } - - /** - * get class array instance. - * - * @param cl ClassLoader instance. - * @param desc desc. - * @return Class[] class array. - * @throws ClassNotFoundException - */ - private static Class[] desc2classArray(ClassLoader cl, String desc) throws ClassNotFoundException { - if (desc.length() == 0) { - return EMPTY_CLASS_ARRAY; - } - - List> cs = new ArrayList>(); - Matcher m = DESC_PATTERN.matcher(desc); - while (m.find()) { - cs.add(desc2class(cl, m.group())); - } - return cs.toArray(EMPTY_CLASS_ARRAY); - } - - /** - * Find method from method signature - * - * @param clazz Target class to find method - * @param methodName Method signature, e.g.: method1(int, String). It is allowed to provide method name only, e.g.: method2 - * @return target method - * @throws NoSuchMethodException - * @throws ClassNotFoundException - * @throws IllegalStateException when multiple methods are found (overridden method when parameter info is not provided) - */ - public static Method findMethodByMethodSignature(Class clazz, String methodName, String[] parameterTypes) - throws NoSuchMethodException, ClassNotFoundException { - String signature = clazz.getName() + "." + methodName; - if (parameterTypes != null && parameterTypes.length > 0) { - signature += StringUtils.join(parameterTypes); - } - Method method = Signature_METHODS_CACHE.get(signature); - if (method != null) { - return method; - } - if (parameterTypes == null) { - List finded = new ArrayList(); - for (Method m : clazz.getMethods()) { - if (m.getName().equals(methodName)) { - finded.add(m); - } - } - if (finded.isEmpty()) { - throw new NoSuchMethodException("No such method " + methodName + " in class " + clazz); - } - if (finded.size() > 1) { - String msg = String.format("Not unique method for method name(%s) in class(%s), find %d methods.", - methodName, clazz.getName(), finded.size()); - throw new IllegalStateException(msg); - } - method = finded.get(0); - } else { - Class[] types = new Class[parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - types[i] = ReflectUtils.name2class(parameterTypes[i]); - } - method = clazz.getMethod(methodName, types); - - } - Signature_METHODS_CACHE.put(signature, method); - return method; - } - - public static Method findMethodByMethodName(Class clazz, String methodName) - throws NoSuchMethodException, ClassNotFoundException { - return findMethodByMethodSignature(clazz, methodName, null); - } - - public static Constructor findConstructor(Class clazz, Class paramType) throws NoSuchMethodException { - Constructor targetConstructor; - try { - targetConstructor = clazz.getConstructor(new Class[]{paramType}); - } catch (NoSuchMethodException e) { - targetConstructor = null; - Constructor[] constructors = clazz.getConstructors(); - for (Constructor constructor : constructors) { - if (Modifier.isPublic(constructor.getModifiers()) - && constructor.getParameterTypes().length == 1 - && constructor.getParameterTypes()[0].isAssignableFrom(paramType)) { - targetConstructor = constructor; - break; - } - } - if (targetConstructor == null) { - throw e; - } - } - return targetConstructor; - } - - /** - * Check if one object is the implementation for a given interface. - *

- * This method will not trigger classloading for the given interface, therefore it will not lead to error when - * the given interface is not visible by the classloader - * - * @param obj Object to examine - * @param interfaceClazzName The given interface - * @return true if the object implements the given interface, otherwise return false - */ - public static boolean isInstance(Object obj, String interfaceClazzName) { - for (Class clazz = obj.getClass(); - clazz != null && !clazz.equals(Object.class); - clazz = clazz.getSuperclass()) { - Class[] interfaces = clazz.getInterfaces(); - for (Class itf : interfaces) { - if (itf.getName().equals(interfaceClazzName)) { - return true; - } - } - } - return false; - } - - public static Object getEmptyObject(Class returnType) { - return getEmptyObject(returnType, new HashMap, Object>(), 0); - } - - private static Object getEmptyObject(Class returnType, Map, Object> emptyInstances, int level) { - if (level > 2) { - return null; - } - if (returnType == null) { - return null; - } else if (returnType == boolean.class || returnType == Boolean.class) { - return false; - } else if (returnType == char.class || returnType == Character.class) { - return '\0'; - } else if (returnType == byte.class || returnType == Byte.class) { - return (byte) 0; - } else if (returnType == short.class || returnType == Short.class) { - return (short) 0; - } else if (returnType == int.class || returnType == Integer.class) { - return 0; - } else if (returnType == long.class || returnType == Long.class) { - return 0L; - } else if (returnType == float.class || returnType == Float.class) { - return 0F; - } else if (returnType == double.class || returnType == Double.class) { - return 0D; - } else if (returnType.isArray()) { - return Array.newInstance(returnType.getComponentType(), 0); - } else if (returnType.isAssignableFrom(ArrayList.class)) { - return new ArrayList(0); - } else if (returnType.isAssignableFrom(HashSet.class)) { - return new HashSet(0); - } else if (returnType.isAssignableFrom(HashMap.class)) { - return new HashMap(0); - } else if (String.class.equals(returnType)) { - return ""; - } else if (!returnType.isInterface()) { - try { - Object value = emptyInstances.get(returnType); - if (value == null) { - value = returnType.newInstance(); - emptyInstances.put(returnType, value); - } - Class cls = value.getClass(); - while (cls != null && cls != Object.class) { - Field[] fields = cls.getDeclaredFields(); - for (Field field : fields) { - if (field.isSynthetic()) { - continue; - } - Object property = getEmptyObject(field.getType(), emptyInstances, level + 1); - if (property != null) { - try { - if (!field.isAccessible()) { - field.setAccessible(true); - } - field.set(value, property); - } catch (Throwable e) { - } - } - } - cls = cls.getSuperclass(); - } - return value; - } catch (Throwable e) { - return null; - } - } else { - return null; - } - } - - public static boolean isBeanPropertyReadMethod(Method method) { - return method != null - && Modifier.isPublic(method.getModifiers()) - && !Modifier.isStatic(method.getModifiers()) - && method.getReturnType() != void.class - && method.getDeclaringClass() != Object.class - && method.getParameterTypes().length == 0 - && ((method.getName().startsWith("get") && method.getName().length() > 3) - || (method.getName().startsWith("is") && method.getName().length() > 2)); - } - - public static String getPropertyNameFromBeanReadMethod(Method method) { - if (isBeanPropertyReadMethod(method)) { - if (method.getName().startsWith("get")) { - return method.getName().substring(3, 4).toLowerCase() - + method.getName().substring(4); - } - if (method.getName().startsWith("is")) { - return method.getName().substring(2, 3).toLowerCase() - + method.getName().substring(3); - } - } - return null; - } - - public static boolean isBeanPropertyWriteMethod(Method method) { - return method != null - && Modifier.isPublic(method.getModifiers()) - && !Modifier.isStatic(method.getModifiers()) - && method.getDeclaringClass() != Object.class - && method.getParameterTypes().length == 1 - && method.getName().startsWith("set") - && method.getName().length() > 3; - } - - public static String getPropertyNameFromBeanWriteMethod(Method method) { - if (isBeanPropertyWriteMethod(method)) { - return method.getName().substring(3, 4).toLowerCase() - + method.getName().substring(4); - } - return null; - } - - public static boolean isPublicInstanceField(Field field) { - return Modifier.isPublic(field.getModifiers()) - && !Modifier.isStatic(field.getModifiers()) - && !Modifier.isFinal(field.getModifiers()) - && !field.isSynthetic(); - } - - public static Map getBeanPropertyFields(Class cl) { - Map properties = new HashMap(); - for (; cl != null; cl = cl.getSuperclass()) { - Field[] fields = cl.getDeclaredFields(); - for (Field field : fields) { - if (Modifier.isTransient(field.getModifiers()) - || Modifier.isStatic(field.getModifiers())) { - continue; - } - - field.setAccessible(true); - - properties.put(field.getName(), field); - } - } - - return properties; - } - - public static Map getBeanPropertyReadMethods(Class cl) { - Map properties = new HashMap(); - for (; cl != null; cl = cl.getSuperclass()) { - Method[] methods = cl.getDeclaredMethods(); - for (Method method : methods) { - if (isBeanPropertyReadMethod(method)) { - method.setAccessible(true); - String property = getPropertyNameFromBeanReadMethod(method); - properties.put(property, method); - } - } - } - - return properties; - } - - public static Type[] getReturnTypes(Method method) { - Class returnType = method.getReturnType(); - Type genericReturnType = method.getGenericReturnType(); - if (Future.class.isAssignableFrom(returnType)) { - if (genericReturnType instanceof ParameterizedType) { - Type actualArgType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0]; - if (actualArgType instanceof ParameterizedType) { - returnType = (Class) ((ParameterizedType) actualArgType).getRawType(); - genericReturnType = actualArgType; - } else { - returnType = (Class) actualArgType; - genericReturnType = returnType; - } - } else { - returnType = null; - genericReturnType = null; - } - } - return new Type[]{returnType, genericReturnType}; - } +/* + * 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.utils; + +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableSet; + +/** + * ReflectUtils + */ +public final class ReflectUtils { + + /** + * void(V). + */ + public static final char JVM_VOID = 'V'; + + /** + * boolean(Z). + */ + public static final char JVM_BOOLEAN = 'Z'; + + /** + * byte(B). + */ + public static final char JVM_BYTE = 'B'; + + /** + * char(C). + */ + public static final char JVM_CHAR = 'C'; + + /** + * double(D). + */ + public static final char JVM_DOUBLE = 'D'; + + /** + * float(F). + */ + public static final char JVM_FLOAT = 'F'; + + /** + * int(I). + */ + public static final char JVM_INT = 'I'; + + /** + * long(J). + */ + public static final char JVM_LONG = 'J'; + + /** + * short(S). + */ + public static final char JVM_SHORT = 'S'; + + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + public static final String JAVA_IDENT_REGEX = "(?:[_$a-zA-Z][_$a-zA-Z0-9]*)"; + + public static final String JAVA_NAME_REGEX = "(?:" + JAVA_IDENT_REGEX + "(?:\\." + JAVA_IDENT_REGEX + ")*)"; + + public static final String CLASS_DESC = "(?:L" + JAVA_IDENT_REGEX + "(?:\\/" + JAVA_IDENT_REGEX + ")*;)"; + + public static final String ARRAY_DESC = "(?:\\[+(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "))"; + + public static final String DESC_REGEX = "(?:(?:[VZBCDFIJS])|" + CLASS_DESC + "|" + ARRAY_DESC + ")"; + + public static final Pattern DESC_PATTERN = Pattern.compile(DESC_REGEX); + + public static final String METHOD_DESC_REGEX = "(?:(" + JAVA_IDENT_REGEX + ")?\\((" + DESC_REGEX + "*)\\)(" + DESC_REGEX + ")?)"; + + public static final Pattern METHOD_DESC_PATTERN = Pattern.compile(METHOD_DESC_REGEX); + + public static final Pattern GETTER_METHOD_DESC_PATTERN = Pattern.compile("get([A-Z][_a-zA-Z0-9]*)\\(\\)(" + DESC_REGEX + ")"); + + public static final Pattern SETTER_METHOD_DESC_PATTERN = Pattern.compile("set([A-Z][_a-zA-Z0-9]*)\\((" + DESC_REGEX + ")\\)V"); + + public static final Pattern IS_HAS_CAN_METHOD_DESC_PATTERN = Pattern.compile("(?:is|has|can)([A-Z][_a-zA-Z0-9]*)\\(\\)Z"); + + private static final ConcurrentMap> DESC_CLASS_CACHE = new ConcurrentHashMap>(); + + private static final ConcurrentMap> NAME_CLASS_CACHE = new ConcurrentHashMap>(); + + private static final ConcurrentMap Signature_METHODS_CACHE = new ConcurrentHashMap(); + + private ReflectUtils() { + } + + public static boolean isPrimitives(Class cls) { + if (cls.isArray()) { + return isPrimitive(cls.getComponentType()); + } + return isPrimitive(cls); + } + + public static boolean isPrimitive(Class cls) { + return cls.isPrimitive() || cls == String.class || cls == Boolean.class || cls == Character.class + || Number.class.isAssignableFrom(cls) || Date.class.isAssignableFrom(cls); + } + + public static Class getBoxedClass(Class c) { + if (c == int.class) { + c = Integer.class; + } else if (c == boolean.class) { + c = Boolean.class; + } else if (c == long.class) { + c = Long.class; + } else if (c == float.class) { + c = Float.class; + } else if (c == double.class) { + c = Double.class; + } else if (c == char.class) { + c = Character.class; + } else if (c == byte.class) { + c = Byte.class; + } else if (c == short.class) { + c = Short.class; + } + return c; + } + + /** + * is compatible. + * + * @param c class. + * @param o instance. + * @return compatible or not. + */ + public static boolean isCompatible(Class c, Object o) { + boolean pt = c.isPrimitive(); + if (o == null) { + return !pt; + } + + if (pt) { + c = getBoxedClass(c); + } + + return c == o.getClass() || c.isInstance(o); + } + + /** + * is compatible. + * + * @param cs class array. + * @param os object array. + * @return compatible or not. + */ + public static boolean isCompatible(Class[] cs, Object[] os) { + int len = cs.length; + if (len != os.length) { + return false; + } + if (len == 0) { + return true; + } + for (int i = 0; i < len; i++) { + if (!isCompatible(cs[i], os[i])) { + return false; + } + } + return true; + } + + public static String getCodeBase(Class cls) { + if (cls == null) { + return null; + } + ProtectionDomain domain = cls.getProtectionDomain(); + if (domain == null) { + return null; + } + CodeSource source = domain.getCodeSource(); + if (source == null) { + return null; + } + URL location = source.getLocation(); + if (location == null) { + return null; + } + return location.getFile(); + } + + /** + * get name. + * java.lang.Object[][].class => "java.lang.Object[][]" + * + * @param c class. + * @return name. + */ + public static String getName(Class c) { + if (c.isArray()) { + StringBuilder sb = new StringBuilder(); + do { + sb.append("[]"); + c = c.getComponentType(); + } + while (c.isArray()); + + return c.getName() + sb.toString(); + } + return c.getName(); + } + + public static Class getGenericClass(Class cls) { + return getGenericClass(cls, 0); + } + + public static Class getGenericClass(Class cls, int i) { + try { + ParameterizedType parameterizedType = ((ParameterizedType) cls.getGenericInterfaces()[0]); + Object genericClass = parameterizedType.getActualTypeArguments()[i]; + if (genericClass instanceof ParameterizedType) { // handle nested generic type + return (Class) ((ParameterizedType) genericClass).getRawType(); + } else if (genericClass instanceof GenericArrayType) { // handle array generic type + return (Class) ((GenericArrayType) genericClass).getGenericComponentType(); + } else if (((Class) genericClass).isArray()) { + // Requires JDK 7 or higher, Foo is no longer GenericArrayType + return ((Class) genericClass).getComponentType(); + } else { + return (Class) genericClass; + } + } catch (Throwable e) { + throw new IllegalArgumentException(cls.getName() + + " generic type undefined!", e); + } + } + + /** + * get method name. + * "void do(int)", "void do()", "int do(java.lang.String,boolean)" + * + * @param m method. + * @return name. + */ + public static String getName(final Method m) { + StringBuilder ret = new StringBuilder(); + ret.append(getName(m.getReturnType())).append(' '); + ret.append(m.getName()).append('('); + Class[] parameterTypes = m.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + if (i > 0) { + ret.append(','); + } + ret.append(getName(parameterTypes[i])); + } + ret.append(')'); + return ret.toString(); + } + + public static String getSignature(String methodName, Class[] parameterTypes) { + StringBuilder sb = new StringBuilder(methodName); + sb.append("("); + if (parameterTypes != null && parameterTypes.length > 0) { + boolean first = true; + for (Class type : parameterTypes) { + if (first) { + first = false; + } else { + sb.append(","); + } + sb.append(type.getName()); + } + } + sb.append(")"); + return sb.toString(); + } + + /** + * get constructor name. + * "()", "(java.lang.String,int)" + * + * @param c constructor. + * @return name. + */ + public static String getName(final Constructor c) { + StringBuilder ret = new StringBuilder("("); + Class[] parameterTypes = c.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + if (i > 0) { + ret.append(','); + } + ret.append(getName(parameterTypes[i])); + } + ret.append(')'); + return ret.toString(); + } + + /** + * get class desc. + * boolean[].class => "[Z" + * Object.class => "Ljava/lang/Object;" + * + * @param c class. + * @return desc. + * @throws NotFoundException + */ + public static String getDesc(Class c) { + StringBuilder ret = new StringBuilder(); + + while (c.isArray()) { + ret.append('['); + c = c.getComponentType(); + } + + if (c.isPrimitive()) { + String t = c.getName(); + if ("void".equals(t)) { + ret.append(JVM_VOID); + } else if ("boolean".equals(t)) { + ret.append(JVM_BOOLEAN); + } else if ("byte".equals(t)) { + ret.append(JVM_BYTE); + } else if ("char".equals(t)) { + ret.append(JVM_CHAR); + } else if ("double".equals(t)) { + ret.append(JVM_DOUBLE); + } else if ("float".equals(t)) { + ret.append(JVM_FLOAT); + } else if ("int".equals(t)) { + ret.append(JVM_INT); + } else if ("long".equals(t)) { + ret.append(JVM_LONG); + } else if ("short".equals(t)) { + ret.append(JVM_SHORT); + } + } else { + ret.append('L'); + ret.append(c.getName().replace('.', '/')); + ret.append(';'); + } + return ret.toString(); + } + + /** + * get class array desc. + * [int.class, boolean[].class, Object.class] => "I[ZLjava/lang/Object;" + * + * @param cs class array. + * @return desc. + * @throws NotFoundException + */ + public static String getDesc(final Class[] cs) { + if (cs.length == 0) { + return ""; + } + + StringBuilder sb = new StringBuilder(64); + for (Class c : cs) { + sb.append(getDesc(c)); + } + return sb.toString(); + } + + /** + * get method desc. + * int do(int arg1) => "do(I)I" + * void do(String arg1,boolean arg2) => "do(Ljava/lang/String;Z)V" + * + * @param m method. + * @return desc. + */ + public static String getDesc(final Method m) { + StringBuilder ret = new StringBuilder(m.getName()).append('('); + Class[] parameterTypes = m.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append(getDesc(m.getReturnType())); + return ret.toString(); + } + + /** + * get constructor desc. + * "()V", "(Ljava/lang/String;I)V" + * + * @param c constructor. + * @return desc + */ + public static String getDesc(final Constructor c) { + StringBuilder ret = new StringBuilder("("); + Class[] parameterTypes = c.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append('V'); + return ret.toString(); + } + + /** + * get method desc. + * "(I)I", "()V", "(Ljava/lang/String;Z)V" + * + * @param m method. + * @return desc. + */ + public static String getDescWithoutMethodName(Method m) { + StringBuilder ret = new StringBuilder(); + ret.append('('); + Class[] parameterTypes = m.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append(getDesc(m.getReturnType())); + return ret.toString(); + } + + /** + * get class desc. + * Object.class => "Ljava/lang/Object;" + * boolean[].class => "[Z" + * + * @param c class. + * @return desc. + * @throws NotFoundException + */ + public static String getDesc(final CtClass c) throws NotFoundException { + StringBuilder ret = new StringBuilder(); + if (c.isArray()) { + ret.append('['); + ret.append(getDesc(c.getComponentType())); + } else if (c.isPrimitive()) { + String t = c.getName(); + if ("void".equals(t)) { + ret.append(JVM_VOID); + } else if ("boolean".equals(t)) { + ret.append(JVM_BOOLEAN); + } else if ("byte".equals(t)) { + ret.append(JVM_BYTE); + } else if ("char".equals(t)) { + ret.append(JVM_CHAR); + } else if ("double".equals(t)) { + ret.append(JVM_DOUBLE); + } else if ("float".equals(t)) { + ret.append(JVM_FLOAT); + } else if ("int".equals(t)) { + ret.append(JVM_INT); + } else if ("long".equals(t)) { + ret.append(JVM_LONG); + } else if ("short".equals(t)) { + ret.append(JVM_SHORT); + } + } else { + ret.append('L'); + ret.append(c.getName().replace('.', '/')); + ret.append(';'); + } + return ret.toString(); + } + + /** + * get method desc. + * "do(I)I", "do()V", "do(Ljava/lang/String;Z)V" + * + * @param m method. + * @return desc. + */ + public static String getDesc(final CtMethod m) throws NotFoundException { + StringBuilder ret = new StringBuilder(m.getName()).append('('); + CtClass[] parameterTypes = m.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append(getDesc(m.getReturnType())); + return ret.toString(); + } + + /** + * get constructor desc. + * "()V", "(Ljava/lang/String;I)V" + * + * @param c constructor. + * @return desc + */ + public static String getDesc(final CtConstructor c) throws NotFoundException { + StringBuilder ret = new StringBuilder("("); + CtClass[] parameterTypes = c.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append('V'); + return ret.toString(); + } + + /** + * get method desc. + * "(I)I", "()V", "(Ljava/lang/String;Z)V". + * + * @param m method. + * @return desc. + */ + public static String getDescWithoutMethodName(final CtMethod m) throws NotFoundException { + StringBuilder ret = new StringBuilder(); + ret.append('('); + CtClass[] parameterTypes = m.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + ret.append(getDesc(parameterTypes[i])); + } + ret.append(')').append(getDesc(m.getReturnType())); + return ret.toString(); + } + + /** + * name to desc. + * java.util.Map[][] => "[[Ljava/util/Map;" + * + * @param name name. + * @return desc. + */ + public static String name2desc(String name) { + StringBuilder sb = new StringBuilder(); + int c = 0, index = name.indexOf('['); + if (index > 0) { + c = (name.length() - index) / 2; + name = name.substring(0, index); + } + while (c-- > 0) { + sb.append("["); + } + if ("void".equals(name)) { + sb.append(JVM_VOID); + } else if ("boolean".equals(name)) { + sb.append(JVM_BOOLEAN); + } else if ("byte".equals(name)) { + sb.append(JVM_BYTE); + } else if ("char".equals(name)) { + sb.append(JVM_CHAR); + } else if ("double".equals(name)) { + sb.append(JVM_DOUBLE); + } else if ("float".equals(name)) { + sb.append(JVM_FLOAT); + } else if ("int".equals(name)) { + sb.append(JVM_INT); + } else if ("long".equals(name)) { + sb.append(JVM_LONG); + } else if ("short".equals(name)) { + sb.append(JVM_SHORT); + } else { + sb.append('L').append(name.replace('.', '/')).append(';'); + } + return sb.toString(); + } + + /** + * desc to name. + * "[[I" => "int[][]" + * + * @param desc desc. + * @return name. + */ + public static String desc2name(String desc) { + StringBuilder sb = new StringBuilder(); + int c = desc.lastIndexOf('[') + 1; + if (desc.length() == c + 1) { + switch (desc.charAt(c)) { + case JVM_VOID: { + sb.append("void"); + break; + } + case JVM_BOOLEAN: { + sb.append("boolean"); + break; + } + case JVM_BYTE: { + sb.append("byte"); + break; + } + case JVM_CHAR: { + sb.append("char"); + break; + } + case JVM_DOUBLE: { + sb.append("double"); + break; + } + case JVM_FLOAT: { + sb.append("float"); + break; + } + case JVM_INT: { + sb.append("int"); + break; + } + case JVM_LONG: { + sb.append("long"); + break; + } + case JVM_SHORT: { + sb.append("short"); + break; + } + default: + throw new RuntimeException(); + } + } else { + sb.append(desc.substring(c + 1, desc.length() - 1).replace('/', '.')); + } + while (c-- > 0) { + sb.append("[]"); + } + return sb.toString(); + } + + public static Class forName(String name) { + try { + return name2class(name); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Not found class " + name + ", cause: " + e.getMessage(), e); + } + } + + public static Class forName(ClassLoader cl, String name) { + try { + return name2class(cl, name); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Not found class " + name + ", cause: " + e.getMessage(), e); + } + } + + /** + * name to class. + * "boolean" => boolean.class + * "java.util.Map[][]" => java.util.Map[][].class + * + * @param name name. + * @return Class instance. + */ + public static Class name2class(String name) throws ClassNotFoundException { + return name2class(ClassUtils.getClassLoader(), name); + } + + /** + * name to class. + * "boolean" => boolean.class + * "java.util.Map[][]" => java.util.Map[][].class + * + * @param cl ClassLoader instance. + * @param name name. + * @return Class instance. + */ + private static Class name2class(ClassLoader cl, String name) throws ClassNotFoundException { + int c = 0, index = name.indexOf('['); + if (index > 0) { + c = (name.length() - index) / 2; + name = name.substring(0, index); + } + if (c > 0) { + StringBuilder sb = new StringBuilder(); + while (c-- > 0) { + sb.append("["); + } + + if ("void".equals(name)) { + sb.append(JVM_VOID); + } else if ("boolean".equals(name)) { + sb.append(JVM_BOOLEAN); + } else if ("byte".equals(name)) { + sb.append(JVM_BYTE); + } else if ("char".equals(name)) { + sb.append(JVM_CHAR); + } else if ("double".equals(name)) { + sb.append(JVM_DOUBLE); + } else if ("float".equals(name)) { + sb.append(JVM_FLOAT); + } else if ("int".equals(name)) { + sb.append(JVM_INT); + } else if ("long".equals(name)) { + sb.append(JVM_LONG); + } else if ("short".equals(name)) { + sb.append(JVM_SHORT); + } else { + sb.append('L').append(name).append(';'); // "java.lang.Object" ==> "Ljava.lang.Object;" + } + name = sb.toString(); + } else { + if ("void".equals(name)) { + return void.class; + } else if ("boolean".equals(name)) { + return boolean.class; + } else if ("byte".equals(name)) { + return byte.class; + } else if ("char".equals(name)) { + return char.class; + } else if ("double".equals(name)) { + return double.class; + } else if ("float".equals(name)) { + return float.class; + } else if ("int".equals(name)) { + return int.class; + } else if ("long".equals(name)) { + return long.class; + } else if ("short".equals(name)) { + return short.class; + } + } + + if (cl == null) { + cl = ClassUtils.getClassLoader(); + } + Class clazz = NAME_CLASS_CACHE.get(name); + if (clazz == null) { + clazz = Class.forName(name, true, cl); + NAME_CLASS_CACHE.put(name, clazz); + } + return clazz; + } + + /** + * desc to class. + * "[Z" => boolean[].class + * "[[Ljava/util/Map;" => java.util.Map[][].class + * + * @param desc desc. + * @return Class instance. + * @throws ClassNotFoundException + */ + public static Class desc2class(String desc) throws ClassNotFoundException { + return desc2class(ClassUtils.getClassLoader(), desc); + } + + /** + * desc to class. + * "[Z" => boolean[].class + * "[[Ljava/util/Map;" => java.util.Map[][].class + * + * @param cl ClassLoader instance. + * @param desc desc. + * @return Class instance. + * @throws ClassNotFoundException + */ + private static Class desc2class(ClassLoader cl, String desc) throws ClassNotFoundException { + switch (desc.charAt(0)) { + case JVM_VOID: + return void.class; + case JVM_BOOLEAN: + return boolean.class; + case JVM_BYTE: + return byte.class; + case JVM_CHAR: + return char.class; + case JVM_DOUBLE: + return double.class; + case JVM_FLOAT: + return float.class; + case JVM_INT: + return int.class; + case JVM_LONG: + return long.class; + case JVM_SHORT: + return short.class; + case 'L': + desc = desc.substring(1, desc.length() - 1).replace('/', '.'); // "Ljava/lang/Object;" ==> "java.lang.Object" + break; + case '[': + desc = desc.replace('/', '.'); // "[[Ljava/lang/Object;" ==> "[[Ljava.lang.Object;" + break; + default: + throw new ClassNotFoundException("Class not found: " + desc); + } + + if (cl == null) { + cl = ClassUtils.getClassLoader(); + } + Class clazz = DESC_CLASS_CACHE.get(desc); + if (clazz == null) { + clazz = Class.forName(desc, true, cl); + DESC_CLASS_CACHE.put(desc, clazz); + } + return clazz; + } + + /** + * get class array instance. + * + * @param desc desc. + * @return Class class array. + * @throws ClassNotFoundException + */ + public static Class[] desc2classArray(String desc) throws ClassNotFoundException { + Class[] ret = desc2classArray(ClassUtils.getClassLoader(), desc); + return ret; + } + + /** + * get class array instance. + * + * @param cl ClassLoader instance. + * @param desc desc. + * @return Class[] class array. + * @throws ClassNotFoundException + */ + private static Class[] desc2classArray(ClassLoader cl, String desc) throws ClassNotFoundException { + if (desc.length() == 0) { + return EMPTY_CLASS_ARRAY; + } + + List> cs = new ArrayList>(); + Matcher m = DESC_PATTERN.matcher(desc); + while (m.find()) { + cs.add(desc2class(cl, m.group())); + } + return cs.toArray(EMPTY_CLASS_ARRAY); + } + + /** + * Find method from method signature + * + * @param clazz Target class to find method + * @param methodName Method signature, e.g.: method1(int, String). It is allowed to provide method name only, e.g.: method2 + * @return target method + * @throws NoSuchMethodException + * @throws ClassNotFoundException + * @throws IllegalStateException when multiple methods are found (overridden method when parameter info is not provided) + */ + public static Method findMethodByMethodSignature(Class clazz, String methodName, String[] parameterTypes) + throws NoSuchMethodException, ClassNotFoundException { + String signature = clazz.getName() + "." + methodName; + if (parameterTypes != null && parameterTypes.length > 0) { + signature += StringUtils.join(parameterTypes); + } + Method method = Signature_METHODS_CACHE.get(signature); + if (method != null) { + return method; + } + if (parameterTypes == null) { + List finded = new ArrayList(); + for (Method m : clazz.getMethods()) { + if (m.getName().equals(methodName)) { + finded.add(m); + } + } + if (finded.isEmpty()) { + throw new NoSuchMethodException("No such method " + methodName + " in class " + clazz); + } + if (finded.size() > 1) { + String msg = String.format("Not unique method for method name(%s) in class(%s), find %d methods.", + methodName, clazz.getName(), finded.size()); + throw new IllegalStateException(msg); + } + method = finded.get(0); + } else { + Class[] types = new Class[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + types[i] = ReflectUtils.name2class(parameterTypes[i]); + } + method = clazz.getMethod(methodName, types); + + } + Signature_METHODS_CACHE.put(signature, method); + return method; + } + + public static Method findMethodByMethodName(Class clazz, String methodName) + throws NoSuchMethodException, ClassNotFoundException { + return findMethodByMethodSignature(clazz, methodName, null); + } + + public static Constructor findConstructor(Class clazz, Class paramType) throws NoSuchMethodException { + Constructor targetConstructor; + try { + targetConstructor = clazz.getConstructor(new Class[]{paramType}); + } catch (NoSuchMethodException e) { + targetConstructor = null; + Constructor[] constructors = clazz.getConstructors(); + for (Constructor constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers()) + && constructor.getParameterTypes().length == 1 + && constructor.getParameterTypes()[0].isAssignableFrom(paramType)) { + targetConstructor = constructor; + break; + } + } + if (targetConstructor == null) { + throw e; + } + } + return targetConstructor; + } + + /** + * Check if one object is the implementation for a given interface. + *

+ * This method will not trigger classloading for the given interface, therefore it will not lead to error when + * the given interface is not visible by the classloader + * + * @param obj Object to examine + * @param interfaceClazzName The given interface + * @return true if the object implements the given interface, otherwise return false + */ + public static boolean isInstance(Object obj, String interfaceClazzName) { + for (Class clazz = obj.getClass(); + clazz != null && !clazz.equals(Object.class); + clazz = clazz.getSuperclass()) { + Class[] interfaces = clazz.getInterfaces(); + for (Class itf : interfaces) { + if (itf.getName().equals(interfaceClazzName)) { + return true; + } + } + } + return false; + } + + public static Object getEmptyObject(Class returnType) { + return getEmptyObject(returnType, new HashMap, Object>(), 0); + } + + private static Object getEmptyObject(Class returnType, Map, Object> emptyInstances, int level) { + if (level > 2) { + return null; + } + if (returnType == null) { + return null; + } else if (returnType == boolean.class || returnType == Boolean.class) { + return false; + } else if (returnType == char.class || returnType == Character.class) { + return '\0'; + } else if (returnType == byte.class || returnType == Byte.class) { + return (byte) 0; + } else if (returnType == short.class || returnType == Short.class) { + return (short) 0; + } else if (returnType == int.class || returnType == Integer.class) { + return 0; + } else if (returnType == long.class || returnType == Long.class) { + return 0L; + } else if (returnType == float.class || returnType == Float.class) { + return 0F; + } else if (returnType == double.class || returnType == Double.class) { + return 0D; + } else if (returnType.isArray()) { + return Array.newInstance(returnType.getComponentType(), 0); + } else if (returnType.isAssignableFrom(ArrayList.class)) { + return new ArrayList(0); + } else if (returnType.isAssignableFrom(HashSet.class)) { + return new HashSet(0); + } else if (returnType.isAssignableFrom(HashMap.class)) { + return new HashMap(0); + } else if (String.class.equals(returnType)) { + return ""; + } else if (!returnType.isInterface()) { + try { + Object value = emptyInstances.get(returnType); + if (value == null) { + value = returnType.newInstance(); + emptyInstances.put(returnType, value); + } + Class cls = value.getClass(); + while (cls != null && cls != Object.class) { + Field[] fields = cls.getDeclaredFields(); + for (Field field : fields) { + if (field.isSynthetic()) { + continue; + } + Object property = getEmptyObject(field.getType(), emptyInstances, level + 1); + if (property != null) { + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + field.set(value, property); + } catch (Throwable e) { + } + } + } + cls = cls.getSuperclass(); + } + return value; + } catch (Throwable e) { + return null; + } + } else { + return null; + } + } + + public static boolean isBeanPropertyReadMethod(Method method) { + return method != null + && Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getReturnType() != void.class + && method.getDeclaringClass() != Object.class + && method.getParameterTypes().length == 0 + && ((method.getName().startsWith("get") && method.getName().length() > 3) + || (method.getName().startsWith("is") && method.getName().length() > 2)); + } + + public static String getPropertyNameFromBeanReadMethod(Method method) { + if (isBeanPropertyReadMethod(method)) { + if (method.getName().startsWith("get")) { + return method.getName().substring(3, 4).toLowerCase() + + method.getName().substring(4); + } + if (method.getName().startsWith("is")) { + return method.getName().substring(2, 3).toLowerCase() + + method.getName().substring(3); + } + } + return null; + } + + public static boolean isBeanPropertyWriteMethod(Method method) { + return method != null + && Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getDeclaringClass() != Object.class + && method.getParameterTypes().length == 1 + && method.getName().startsWith("set") + && method.getName().length() > 3; + } + + public static String getPropertyNameFromBeanWriteMethod(Method method) { + if (isBeanPropertyWriteMethod(method)) { + return method.getName().substring(3, 4).toLowerCase() + + method.getName().substring(4); + } + return null; + } + + public static boolean isPublicInstanceField(Field field) { + return Modifier.isPublic(field.getModifiers()) + && !Modifier.isStatic(field.getModifiers()) + && !Modifier.isFinal(field.getModifiers()) + && !field.isSynthetic(); + } + + public static Map getBeanPropertyFields(Class cl) { + Map properties = new HashMap(); + for (; cl != null; cl = cl.getSuperclass()) { + Field[] fields = cl.getDeclaredFields(); + for (Field field : fields) { + if (Modifier.isTransient(field.getModifiers()) + || Modifier.isStatic(field.getModifiers())) { + continue; + } + + field.setAccessible(true); + + properties.put(field.getName(), field); + } + } + + return properties; + } + + public static Map getBeanPropertyReadMethods(Class cl) { + Map properties = new HashMap(); + for (; cl != null; cl = cl.getSuperclass()) { + Method[] methods = cl.getDeclaredMethods(); + for (Method method : methods) { + if (isBeanPropertyReadMethod(method)) { + method.setAccessible(true); + String property = getPropertyNameFromBeanReadMethod(method); + properties.put(property, method); + } + } + } + + return properties; + } + + public static Type[] getReturnTypes(Method method) { + Class returnType = method.getReturnType(); + Type genericReturnType = method.getGenericReturnType(); + if (Future.class.isAssignableFrom(returnType)) { + if (genericReturnType instanceof ParameterizedType) { + Type actualArgType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0]; + if (actualArgType instanceof ParameterizedType) { + returnType = (Class) ((ParameterizedType) actualArgType).getRawType(); + genericReturnType = actualArgType; + } else { + returnType = (Class) actualArgType; + genericReturnType = returnType; + } + } else { + returnType = null; + genericReturnType = null; + } + } + return new Type[]{returnType, genericReturnType}; + } + + /** + * Find the {@link Set} of {@link ParameterizedType} + * + * @param sourceClass the source {@link Class class} + * @return non-null read-only {@link Set} + * @since 2.7.3 + */ + public static Set findParameterizedTypes(Class sourceClass) { + // Add Generic Interfaces + List genericTypes = new LinkedList<>(asList(sourceClass.getGenericInterfaces())); + // Add Generic Super Class + genericTypes.add(sourceClass.getGenericSuperclass()); + + Set parameterizedTypes = genericTypes.stream() + .filter(type -> type instanceof ParameterizedType)// filter ParameterizedType + .map(type -> ParameterizedType.class.cast(type)) // cast to ParameterizedType + .collect(Collectors.toSet()); + + if (parameterizedTypes.isEmpty()) { // If not found, try to search super types recursively + genericTypes.stream() + .filter(type -> type instanceof Class) + .map(type -> Class.class.cast(type)) + .forEach(superClass -> { + parameterizedTypes.addAll(findParameterizedTypes(superClass)); + }); + } + + return unmodifiableSet(parameterizedTypes); // build as a Set + + } + + /** + * Find the hierarchical types form the source {@link Class class} by specified {@link Class type}. + * + * @param sourceClass the source {@link Class class} + * @param matchType the type to match + * @param the type to match + * @return non-null read-only {@link Set} + * @since 2.7.3 + */ + public static Set> findHierarchicalTypes(Class sourceClass, Class matchType) { + if (sourceClass == null) { + return Collections.emptySet(); + } + + Set> hierarchicalTypes = new LinkedHashSet<>(); + + if (matchType.isAssignableFrom(sourceClass)) { + hierarchicalTypes.add((Class) sourceClass); + } + + // Find all super classes + hierarchicalTypes.addAll(findHierarchicalTypes(sourceClass.getSuperclass(), matchType)); + + return unmodifiableSet(hierarchicalTypes); + } } \ No newline at end of file diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java index 56e73cb75a..548c48f439 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/UrlUtils.java @@ -30,19 +30,19 @@ import java.util.stream.Collectors; import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; import static org.apache.dubbo.common.constants.CommonConstants.CLASSIFIER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PASSWORD_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.REGISTRY_SPLIT_PATTERN; import static org.apache.dubbo.common.constants.CommonConstants.REMOVE_VALUE_PREFIX; -import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; -import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PASSWORD_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.USERNAME_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY; diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/DefaultPageTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/DefaultPageTest.java new file mode 100644 index 0000000000..3e6296ba3d --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/DefaultPageTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.common.utils; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * {@link DefaultPage} + * + * @since 2.7.3 + */ +public class DefaultPageTest { + + @Test + public void test() { + List data = asList(1, 2, 3, 4, 5); + DefaultPage page = new DefaultPage(0, 1, data.subList(0, 1), data.size()); + } +} diff --git a/dubbo-compatible/src/test/java/org/apache/dubbo/filter/LegacyInvocation.java b/dubbo-compatible/src/test/java/org/apache/dubbo/filter/LegacyInvocation.java index a98f22e6ef..cd1cc17324 100644 --- a/dubbo-compatible/src/test/java/org/apache/dubbo/filter/LegacyInvocation.java +++ b/dubbo-compatible/src/test/java/org/apache/dubbo/filter/LegacyInvocation.java @@ -23,11 +23,11 @@ import com.alibaba.dubbo.rpc.Invoker; import java.util.HashMap; import java.util.Map; -import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; /** diff --git a/dubbo-config/dubbo-config-api/pom.xml b/dubbo-config/dubbo-config-api/pom.xml index 3a4584d90c..b8f5938b11 100644 --- a/dubbo-config/dubbo-config-api/pom.xml +++ b/dubbo-config/dubbo-config-api/pom.xml @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 org.apache.dubbo @@ -64,6 +65,13 @@ dubbo-filter-cache ${project.parent.version} + + + org.apache.dubbo + dubbo-metadata + ${project.parent.version} + + org.apache.dubbo @@ -71,17 +79,55 @@ ${project.parent.version} test + org.apache.dubbo dubbo-remoting-netty4 ${project.parent.version} test + + + org.apache.dubbo + dubbo-rpc-dubbo + ${project.parent.version} + test + + org.apache.dubbo dubbo-registry-multicast ${project.parent.version} test + + + + org.apache.dubbo + dubbo-registry-zookeeper + ${project.parent.version} + test + + + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${project.parent.version} + test + + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.parent.version} + test + + + + org.apache.curator + curator-test + test + diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java index b72560897c..8130a5fc04 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java @@ -53,31 +53,30 @@ import java.util.Map; import java.util.Set; import static org.apache.dubbo.common.config.ConfigurationUtils.parseProperties; -import static org.apache.dubbo.rpc.cluster.Constants.TAG_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; import static org.apache.dubbo.common.constants.CommonConstants.FILE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; -import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; -import static org.apache.dubbo.config.Constants.LAYER_KEY; -import static org.apache.dubbo.config.Constants.LISTENER_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; -import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; -import static org.apache.dubbo.config.Constants.REGISTRIES_SUFFIX; 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.monitor.Constants.LOGSTAT_PROTOCOL; -import static org.apache.dubbo.registry.Constants.REGISTER_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL; +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; +import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY; +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.REGISTRIES_SUFFIX; +import static org.apache.dubbo.monitor.Constants.LOGSTAT_PROTOCOL; +import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; +import static org.apache.dubbo.registry.Constants.REGISTER_KEY; import static org.apache.dubbo.registry.Constants.SUBSCRIBE_KEY; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; import static org.apache.dubbo.rpc.Constants.INVOKER_LISTENER_KEY; @@ -86,7 +85,8 @@ import static org.apache.dubbo.rpc.Constants.PROXY_KEY; import static org.apache.dubbo.rpc.Constants.REFERENCE_FILTER_KEY; import static org.apache.dubbo.rpc.Constants.RETURN_PREFIX; import static org.apache.dubbo.rpc.Constants.THROW_PREFIX; -import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; +import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.TAG_KEY; /** * AbstractDefaultConfig diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java index 47c035e293..309f907594 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/DubboShutdownHook.java @@ -19,6 +19,8 @@ package org.apache.dubbo.config; import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.config.event.DubboServiceDestroyedEvent; +import org.apache.dubbo.event.EventDispatcher; import org.apache.dubbo.registry.support.AbstractRegistryFactory; import org.apache.dubbo.rpc.Protocol; @@ -42,7 +44,9 @@ public class DubboShutdownHook extends Thread { /** * Has it already been destroyed or not? */ - private final AtomicBoolean destroyed= new AtomicBoolean(false); + private final AtomicBoolean destroyed = new AtomicBoolean(false); + + private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); private DubboShutdownHook(String name) { super(name); @@ -89,6 +93,8 @@ public class DubboShutdownHook extends Thread { AbstractRegistryFactory.destroyAll(); // destroy all the protocols destroyProtocols(); + // dispatch the DubboDestroyedEvent @since 2.7.3 + eventDispatcher.dispatch(new DubboServiceDestroyedEvent(this)); } /** diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ProtocolConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ProtocolConfig.java index c3dcf0c4f9..18c4d1734e 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ProtocolConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ProtocolConfig.java @@ -32,12 +32,12 @@ import org.apache.dubbo.rpc.Protocol; import java.util.Map; -import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; import static org.apache.dubbo.common.constants.CommonConstants.HOST_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.THREADPOOL_KEY; import static org.apache.dubbo.config.Constants.PROTOCOLS_SUFFIX; -import static org.apache.dubbo.remoting.Constants.TELNET; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; +import static org.apache.dubbo.remoting.Constants.TELNET; /** * ProtocolConfig 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 a3c020ceec..ee2e9b63eb 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 @@ -1,664 +1,691 @@ -/* - * 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; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.Version; -import org.apache.dubbo.common.bytecode.Wrapper; -import org.apache.dubbo.common.extension.ExtensionLoader; -import org.apache.dubbo.common.utils.ClassUtils; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.common.utils.ConfigUtils; -import org.apache.dubbo.common.utils.NetUtils; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.config.annotation.Reference; -import org.apache.dubbo.config.context.ConfigManager; -import org.apache.dubbo.config.support.Parameter; -import org.apache.dubbo.metadata.integration.MetadataReportService; -import org.apache.dubbo.remoting.Constants; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Protocol; -import org.apache.dubbo.rpc.ProxyFactory; -import org.apache.dubbo.rpc.cluster.Cluster; -import org.apache.dubbo.rpc.cluster.directory.StaticDirectory; -import org.apache.dubbo.rpc.cluster.support.ClusterUtils; -import org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster; -import org.apache.dubbo.rpc.model.ApplicationModel; -import org.apache.dubbo.rpc.model.ConsumerModel; -import org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol; -import org.apache.dubbo.rpc.service.GenericService; -import org.apache.dubbo.rpc.support.ProtocolUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR; -import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO; -import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.SEMICOLON_SPLIT_PATTERN; -import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; -import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY; -import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; -import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; -import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL; -import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL; -import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL; -import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost; - -/** - * ReferenceConfig - * - * @export - */ -public class ReferenceConfig extends AbstractReferenceConfig { - - private static final long serialVersionUID = -5864351140409987595L; - - /** - * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios. - * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}. - * For example: - * - *
  • when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample, - * then the protocol is RegistryProtocol
  • - * - *
  • when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then - * the protocol is DubboProtocol
  • - *

    - * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two - * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper - */ - private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); - - /** - * The {@link Cluster}'s implementation with adaptive functionality, and actually it will get a {@link Cluster}'s - * specific implementation who is wrapped with MockClusterInvoker - */ - private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); - - /** - * A {@link ProxyFactory} implementation that will generate a reference service's proxy,the JavassistProxyFactory is - * its default implementation - */ - private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); - - /** - * The url of the reference service - */ - private final List urls = new ArrayList(); - - /** - * The interface name of the reference service - */ - private String interfaceName; - - /** - * The interface class of the reference service - */ - private Class interfaceClass; - - /** - * client type - */ - private String client; - - /** - * The url for peer-to-peer invocation - */ - private String url; - - /** - * The method configs - */ - private List methods; - - /** - * The consumer config (default) - */ - private ConsumerConfig consumer; - - /** - * Only the service provider of the specified protocol is invoked, and other protocols are ignored. - */ - private String protocol; - - /** - * The interface proxy reference - */ - private transient volatile T ref; - - /** - * The invoker of the reference service - */ - private transient volatile Invoker invoker; - - /** - * The flag whether the ReferenceConfig has been initialized - */ - private transient volatile boolean initialized; - - /** - * whether this ReferenceConfig has been destroyed - */ - private transient volatile boolean destroyed; - - @SuppressWarnings("unused") - private final Object finalizerGuardian = new Object() { - @Override - protected void finalize() throws Throwable { - super.finalize(); - - if (!ReferenceConfig.this.destroyed) { - logger.warn("ReferenceConfig(" + url + ") is not DESTROYED when FINALIZE"); - - /* don't destroy for now - try { - ReferenceConfig.this.destroy(); - } catch (Throwable t) { - logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ") in finalize method!", t); - } - */ - } - } - }; - - public ReferenceConfig() { - } - - public ReferenceConfig(Reference reference) { - appendAnnotation(Reference.class, reference); - setMethods(MethodConfig.constructMethodConfig(reference.methods())); - } - - public URL toUrl() { - return urls.isEmpty() ? null : urls.iterator().next(); - } - - public List toUrls() { - return urls; - } - - /** - * This method should be called right after the creation of this class's instance, before any property in other config modules is used. - * Check each config modules are created properly and override their properties if necessary. - */ - public void checkAndUpdateSubConfigs() { - if (StringUtils.isEmpty(interfaceName)) { - throw new IllegalStateException(" interface not allow null!"); - } - completeCompoundConfigs(); - startConfigCenter(); - // get consumer's global configuration - checkDefault(); - this.refresh(); - if (getGeneric() == null && getConsumer() != null) { - setGeneric(getConsumer().getGeneric()); - } - if (ProtocolUtils.isGeneric(getGeneric())) { - interfaceClass = GenericService.class; - } else { - try { - interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() - .getContextClassLoader()); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e.getMessage(), e); - } - checkInterfaceAndMethods(interfaceClass, methods); - } - resolveFile(); - checkApplication(); - checkMetadataReport(); - } - - public synchronized T get() { - checkAndUpdateSubConfigs(); - - if (destroyed) { - throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!"); - } - if (ref == null) { - init(); - } - return ref; - } - - public synchronized void destroy() { - if (ref == null) { - return; - } - if (destroyed) { - return; - } - destroyed = true; - try { - invoker.destroy(); - } catch (Throwable t) { - logger.warn("Unexpected error occured when destroy invoker of ReferenceConfig(" + url + ").", t); - } - invoker = null; - ref = null; - } - - private void init() { - if (initialized) { - return; - } - checkStubAndLocal(interfaceClass); - checkMock(interfaceClass); - Map map = new HashMap(); - - map.put(SIDE_KEY, CONSUMER_SIDE); - - appendRuntimeParameters(map); - if (!isGeneric()) { - String revision = Version.getVersion(interfaceClass, version); - if (revision != null && revision.length() > 0) { - map.put(REVISION_KEY, revision); - } - - String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); - if (methods.length == 0) { - logger.warn("No method found in service interface " + interfaceClass.getName()); - map.put(METHODS_KEY, ANY_VALUE); - } else { - map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), COMMA_SEPARATOR)); - } - } - map.put(INTERFACE_KEY, interfaceName); - appendParameters(map, metrics); - appendParameters(map, application); - appendParameters(map, module); - // remove 'default.' prefix for configs from ConsumerConfig - // appendParameters(map, consumer, Constants.DEFAULT_KEY); - appendParameters(map, consumer); - appendParameters(map, this); - Map attributes = null; - if (CollectionUtils.isNotEmpty(methods)) { - attributes = new HashMap(); - for (MethodConfig methodConfig : methods) { - appendParameters(map, methodConfig, methodConfig.getName()); - String retryKey = methodConfig.getName() + ".retry"; - if (map.containsKey(retryKey)) { - String retryValue = map.remove(retryKey); - if ("false".equals(retryValue)) { - map.put(methodConfig.getName() + ".retries", "0"); - } - } - attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig)); - } - } - - String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY); - if (StringUtils.isEmpty(hostToRegistry)) { - hostToRegistry = NetUtils.getLocalHost(); - } else if (isInvalidLocalHost(hostToRegistry)) { - throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); - } - map.put(REGISTER_IP_KEY, hostToRegistry); - - ref = createProxy(map); - - String serviceKey = URL.buildKey(interfaceName, group, version); - ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes)); - initialized = true; - } - - private ConsumerModel buildConsumerModel(String serviceKey, Map attributes) { - Method[] methods = interfaceClass.getMethods(); - Class serviceInterface = interfaceClass; - if (interfaceClass == GenericService.class) { - try { - serviceInterface = Class.forName(interfaceName); - methods = serviceInterface.getMethods(); - } catch (ClassNotFoundException e) { - methods = interfaceClass.getMethods(); - } - } - return new ConsumerModel(serviceKey, serviceInterface, ref, methods, attributes); - } - - @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) - private T createProxy(Map map) { - if (shouldJvmRefer(map)) { - URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map); - invoker = REF_PROTOCOL.refer(interfaceClass, url); - if (logger.isInfoEnabled()) { - logger.info("Using injvm service " + interfaceClass.getName()); - } - } else { - urls.clear(); // reference retry init will add url to urls, lead to OOM - if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address. - String[] us = SEMICOLON_SPLIT_PATTERN.split(url); - if (us != null && us.length > 0) { - for (String u : us) { - URL url = URL.valueOf(u); - if (StringUtils.isEmpty(url.getPath())) { - url = url.setPath(interfaceName); - } - if (REGISTRY_PROTOCOL.equals(url.getProtocol())) { - urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); - } else { - urls.add(ClusterUtils.mergeUrl(url, map)); - } - } - } - } else { // assemble URL from register center's configuration - // if protocols not injvm checkRegistry - if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){ - checkRegistry(); - List us = loadRegistries(false); - if (CollectionUtils.isNotEmpty(us)) { - for (URL u : us) { - URL monitorUrl = loadMonitor(u); - if (monitorUrl != null) { - map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString())); - } - urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); - } - } - if (urls.isEmpty()) { - throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config."); - } - } - } - - if (urls.size() == 1) { - invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); - } else { - List> invokers = new ArrayList>(); - URL registryURL = null; - for (URL url : urls) { - invokers.add(REF_PROTOCOL.refer(interfaceClass, url)); - if (REGISTRY_PROTOCOL.equals(url.getProtocol())) { - registryURL = url; // use last registry url - } - } - if (registryURL != null) { // registry url is available - // use RegistryAwareCluster only when register's CLUSTER is available - URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME); - // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker - invoker = CLUSTER.join(new StaticDirectory(u, invokers)); - } else { // not a registry url, must be direct invoke. - invoker = CLUSTER.join(new StaticDirectory(invokers)); - } - } - } - - if (shouldCheck() && !invoker.isAvailable()) { - throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion()); - } - if (logger.isInfoEnabled()) { - logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); - } - /** - * @since 2.7.0 - * ServiceData Store - */ - MetadataReportService metadataReportService = null; - if ((metadataReportService = getMetadataReportService()) != null) { - URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map); - metadataReportService.publishConsumer(consumerURL); - } - // create service proxy - return (T) PROXY_FACTORY.getProxy(invoker); - } - - /** - * Figure out should refer the service in the same JVM from configurations. The default behavior is true - * 1. if injvm is specified, then use it - * 2. then if a url is specified, then assume it's a remote call - * 3. otherwise, check scope parameter - * 4. if scope is not specified but the target service is provided in the same JVM, then prefer to make the local - * call, which is the default behavior - */ - protected boolean shouldJvmRefer(Map map) { - URL tmpUrl = new URL("temp", "localhost", 0, map); - boolean isJvmRefer; - if (isInjvm() == null) { - // if a url is specified, don't do local reference - if (url != null && url.length() > 0) { - isJvmRefer = false; - } else { - // by default, reference local service if there is - isJvmRefer = InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl); - } - } else { - isJvmRefer = isInjvm(); - } - return isJvmRefer; - } - - protected boolean shouldCheck() { - Boolean shouldCheck = isCheck(); - if (shouldCheck == null && getConsumer() != null) { - shouldCheck = getConsumer().isCheck(); - } - if (shouldCheck == null) { - // default true - shouldCheck = true; - } - return shouldCheck; - } - - protected boolean shouldInit() { - Boolean shouldInit = isInit(); - if (shouldInit == null && getConsumer() != null) { - shouldInit = getConsumer().isInit(); - } - if (shouldInit == null) { - // default is false - return false; - } - return shouldInit; - } - - private void checkDefault() { - if (consumer != null) { - return; - } - setConsumer(ConfigManager.getInstance().getDefaultConsumer().orElseGet(() -> { - ConsumerConfig consumerConfig = new ConsumerConfig(); - consumerConfig.refresh(); - return consumerConfig; - })); - } - - private void completeCompoundConfigs() { - if (consumer != null) { - if (application == null) { - setApplication(consumer.getApplication()); - } - if (module == null) { - setModule(consumer.getModule()); - } - if (registries == null) { - setRegistries(consumer.getRegistries()); - } - if (monitor == null) { - setMonitor(consumer.getMonitor()); - } - } - if (module != null) { - if (registries == null) { - setRegistries(module.getRegistries()); - } - if (monitor == null) { - setMonitor(module.getMonitor()); - } - } - if (application != null) { - if (registries == null) { - setRegistries(application.getRegistries()); - } - if (monitor == null) { - setMonitor(application.getMonitor()); - } - } - } - - public Class getInterfaceClass() { - if (interfaceClass != null) { - return interfaceClass; - } - if (isGeneric() - || (getConsumer() != null && getConsumer().isGeneric())) { - return GenericService.class; - } - try { - if (interfaceName != null && interfaceName.length() > 0) { - this.interfaceClass = Class.forName(interfaceName, true, ClassUtils.getClassLoader()); - } - } catch (ClassNotFoundException t) { - throw new IllegalStateException(t.getMessage(), t); - } - return interfaceClass; - } - - /** - * @param interfaceClass - * @see #setInterface(Class) - * @deprecated - */ - @Deprecated - public void setInterfaceClass(Class interfaceClass) { - setInterface(interfaceClass); - } - - public String getInterface() { - return interfaceName; - } - - public void setInterface(String interfaceName) { - this.interfaceName = interfaceName; - if (StringUtils.isEmpty(id)) { - id = interfaceName; - } - } - - public void setInterface(Class interfaceClass) { - if (interfaceClass != null && !interfaceClass.isInterface()) { - throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!"); - } - this.interfaceClass = interfaceClass; - setInterface(interfaceClass == null ? null : interfaceClass.getName()); - } - - public String getClient() { - return client; - } - - public void setClient(String client) { - checkName(Constants.CLIENT_KEY, client); - this.client = client; - } - - @Parameter(excluded = true) - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public List getMethods() { - return methods; - } - - @SuppressWarnings("unchecked") - public void setMethods(List methods) { - this.methods = (List) methods; - } - - public ConsumerConfig getConsumer() { - return consumer; - } - - public void setConsumer(ConsumerConfig consumer) { - ConfigManager.getInstance().addConsumer(consumer); - this.consumer = consumer; - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - // just for test - Invoker getInvoker() { - return invoker; - } - - @Override - @Parameter(excluded = true) - public String getPrefix() { - return DUBBO + ".reference." + interfaceName; - } - - private void resolveFile() { - String resolve = System.getProperty(interfaceName); - String resolveFile = null; - if (StringUtils.isEmpty(resolve)) { - resolveFile = System.getProperty("dubbo.resolve.file"); - if (StringUtils.isEmpty(resolveFile)) { - File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties"); - if (userResolveFile.exists()) { - resolveFile = userResolveFile.getAbsolutePath(); - } - } - if (resolveFile != null && resolveFile.length() > 0) { - Properties properties = new Properties(); - try (FileInputStream fis = new FileInputStream(new File(resolveFile))) { - properties.load(fis); - } catch (IOException e) { - throw new IllegalStateException("Failed to load " + resolveFile + ", cause: " + e.getMessage(), e); - } - - resolve = properties.getProperty(interfaceName); - } - } - if (resolve != null && resolve.length() > 0) { - url = resolve; - if (logger.isWarnEnabled()) { - if (resolveFile != null) { - logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service."); - } else { - logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service."); - } - } - } - } -} +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.Version; +import org.apache.dubbo.common.bytecode.Wrapper; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.ClassUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.ConfigUtils; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.config.annotation.Reference; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.event.ReferenceConfigDestroyedEvent; +import org.apache.dubbo.config.event.ReferenceConfigInitializedEvent; +import org.apache.dubbo.config.support.Parameter; +import org.apache.dubbo.event.Event; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.metadata.integration.MetadataReportService; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.cluster.Cluster; +import org.apache.dubbo.rpc.cluster.directory.StaticDirectory; +import org.apache.dubbo.rpc.cluster.support.ClusterUtils; +import org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.ConsumerModel; +import org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol; +import org.apache.dubbo.rpc.service.GenericService; +import org.apache.dubbo.rpc.support.ProtocolUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +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; +import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO; +import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.SEMICOLON_SPLIT_PATTERN; +import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL; +import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost; +import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY; +import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL; +import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; +import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL; +import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; + +/** + * ReferenceConfig + * + * @export + */ +public class ReferenceConfig extends AbstractReferenceConfig { + + private static final long serialVersionUID = -5864351140409987595L; + + /** + * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios. + * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}. + * For example: + * + *

  • when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample, + * then the protocol is RegistryProtocol
  • + * + *
  • when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then + * the protocol is DubboProtocol
  • + *

    + * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two + * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper + */ + private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); + + /** + * The {@link Cluster}'s implementation with adaptive functionality, and actually it will get a {@link Cluster}'s + * specific implementation who is wrapped with MockClusterInvoker + */ + private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension(); + + /** + * A {@link ProxyFactory} implementation that will generate a reference service's proxy,the JavassistProxyFactory is + * its default implementation + */ + private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + + /** + * The url of the reference service + */ + private final List urls = new ArrayList(); + + /** + * The interface name of the reference service + */ + private String interfaceName; + + /** + * The interface class of the reference service + */ + private Class interfaceClass; + + /** + * client type + */ + private String client; + + /** + * The url for peer-to-peer invocation + */ + private String url; + + /** + * The method configs + */ + private List methods; + + /** + * The consumer config (default) + */ + private ConsumerConfig consumer; + + /** + * Only the service provider of the specified protocol is invoked, and other protocols are ignored. + */ + private String protocol; + + /** + * The interface proxy reference + */ + private transient volatile T ref; + + /** + * The invoker of the reference service + */ + private transient volatile Invoker invoker; + + /** + * The flag whether the ReferenceConfig has been initialized + */ + private transient volatile boolean initialized; + + /** + * whether this ReferenceConfig has been destroyed + */ + private transient volatile boolean destroyed; + + /** + * The {@link EventDispatcher} + * + * @since 2.7.3 + */ + private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + + @SuppressWarnings("unused") + private final Object finalizerGuardian = new Object() { + @Override + protected void finalize() throws Throwable { + super.finalize(); + + if (!ReferenceConfig.this.destroyed) { + logger.warn("ReferenceConfig(" + url + ") is not DESTROYED when FINALIZE"); + + /* don't destroy for now + try { + ReferenceConfig.this.destroy(); + } catch (Throwable t) { + logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ") in finalize method!", t); + } + */ + } + } + }; + + public ReferenceConfig() { + } + + public ReferenceConfig(Reference reference) { + appendAnnotation(Reference.class, reference); + setMethods(MethodConfig.constructMethodConfig(reference.methods())); + } + + public URL toUrl() { + return urls.isEmpty() ? null : urls.iterator().next(); + } + + public List toUrls() { + return urls; + } + + /** + * This method should be called right after the creation of this class's instance, before any property in other config modules is used. + * Check each config modules are created properly and override their properties if necessary. + */ + public void checkAndUpdateSubConfigs() { + if (StringUtils.isEmpty(interfaceName)) { + throw new IllegalStateException(" interface not allow null!"); + } + completeCompoundConfigs(); + startConfigCenter(); + // get consumer's global configuration + checkDefault(); + this.refresh(); + if (getGeneric() == null && getConsumer() != null) { + setGeneric(getConsumer().getGeneric()); + } + if (ProtocolUtils.isGeneric(getGeneric())) { + interfaceClass = GenericService.class; + } else { + try { + interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() + .getContextClassLoader()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + checkInterfaceAndMethods(interfaceClass, methods); + } + resolveFile(); + checkApplication(); + checkMetadataReport(); + } + + public synchronized T get() { + checkAndUpdateSubConfigs(); + + if (destroyed) { + throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!"); + } + if (ref == null) { + init(); + } + return ref; + } + + public synchronized void destroy() { + if (ref == null) { + return; + } + if (destroyed) { + return; + } + destroyed = true; + try { + invoker.destroy(); + } catch (Throwable t) { + logger.warn("Unexpected error occured when destroy invoker of ReferenceConfig(" + url + ").", t); + } + invoker = null; + ref = null; + + // dispatch a ReferenceConfigDestroyedEvent since 2.7.3 + dispatch(new ReferenceConfigDestroyedEvent(this)); + } + + private void init() { + if (initialized) { + return; + } + checkStubAndLocal(interfaceClass); + checkMock(interfaceClass); + Map map = new HashMap(); + + map.put(SIDE_KEY, CONSUMER_SIDE); + + appendRuntimeParameters(map); + if (!isGeneric()) { + String revision = Version.getVersion(interfaceClass, version); + if (revision != null && revision.length() > 0) { + map.put(REVISION_KEY, revision); + } + + String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); + if (methods.length == 0) { + logger.warn("No method found in service interface " + interfaceClass.getName()); + map.put(METHODS_KEY, ANY_VALUE); + } else { + map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), COMMA_SEPARATOR)); + } + } + map.put(INTERFACE_KEY, interfaceName); + appendParameters(map, metrics); + appendParameters(map, application); + appendParameters(map, module); + // remove 'default.' prefix for configs from ConsumerConfig + // appendParameters(map, consumer, Constants.DEFAULT_KEY); + appendParameters(map, consumer); + appendParameters(map, this); + Map attributes = null; + if (CollectionUtils.isNotEmpty(methods)) { + attributes = new HashMap(); + for (MethodConfig methodConfig : methods) { + appendParameters(map, methodConfig, methodConfig.getName()); + String retryKey = methodConfig.getName() + ".retry"; + if (map.containsKey(retryKey)) { + String retryValue = map.remove(retryKey); + if ("false".equals(retryValue)) { + map.put(methodConfig.getName() + ".retries", "0"); + } + } + attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig)); + } + } + + String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY); + if (StringUtils.isEmpty(hostToRegistry)) { + hostToRegistry = NetUtils.getLocalHost(); + } else if (isInvalidLocalHost(hostToRegistry)) { + throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); + } + map.put(REGISTER_IP_KEY, hostToRegistry); + + ref = createProxy(map); + + String serviceKey = URL.buildKey(interfaceName, group, version); + ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes)); + initialized = true; + + // dispatch a ReferenceConfigInitializedEvent since 2.7.3 + dispatch(new ReferenceConfigInitializedEvent(this, invoker)); + } + + private ConsumerModel buildConsumerModel(String serviceKey, Map attributes) { + Method[] methods = interfaceClass.getMethods(); + Class serviceInterface = interfaceClass; + if (interfaceClass == GenericService.class) { + try { + serviceInterface = Class.forName(interfaceName); + methods = serviceInterface.getMethods(); + } catch (ClassNotFoundException e) { + methods = interfaceClass.getMethods(); + } + } + return new ConsumerModel(serviceKey, serviceInterface, ref, methods, attributes); + } + + @SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) + private T createProxy(Map map) { + if (shouldJvmRefer(map)) { + URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map); + invoker = REF_PROTOCOL.refer(interfaceClass, url); + if (logger.isInfoEnabled()) { + logger.info("Using injvm service " + interfaceClass.getName()); + } + } else { + urls.clear(); // reference retry init will add url to urls, lead to OOM + if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address. + String[] us = SEMICOLON_SPLIT_PATTERN.split(url); + if (us != null && us.length > 0) { + for (String u : us) { + URL url = URL.valueOf(u); + if (StringUtils.isEmpty(url.getPath())) { + url = url.setPath(interfaceName); + } + if (REGISTRY_PROTOCOL.equals(url.getProtocol())) { + urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); + } else { + urls.add(ClusterUtils.mergeUrl(url, map)); + } + } + } + } else { // assemble URL from register center's configuration + // if protocols not injvm checkRegistry + if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) { + checkRegistry(); + List us = loadRegistries(false); + if (CollectionUtils.isNotEmpty(us)) { + for (URL u : us) { + URL monitorUrl = loadMonitor(u); + if (monitorUrl != null) { + map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString())); + } + urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map))); + } + } + if (urls.isEmpty()) { + throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config."); + } + } + } + + if (urls.size() == 1) { + invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0)); + } else { + List> invokers = new ArrayList>(); + URL registryURL = null; + for (URL url : urls) { + invokers.add(REF_PROTOCOL.refer(interfaceClass, url)); + if (REGISTRY_PROTOCOL.equals(url.getProtocol())) { + registryURL = url; // use last registry url + } + } + if (registryURL != null) { // registry url is available + // use RegistryAwareCluster only when register's CLUSTER is available + URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME); + // The invoker wrap relation would be: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker + invoker = CLUSTER.join(new StaticDirectory(u, invokers)); + } else { // not a registry url, must be direct invoke. + invoker = CLUSTER.join(new StaticDirectory(invokers)); + } + } + } + + if (shouldCheck() && !invoker.isAvailable()) { + throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion()); + } + if (logger.isInfoEnabled()) { + logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl()); + } + /** + * @since 2.7.0 + * ServiceData Store + */ + MetadataReportService metadataReportService = null; + if ((metadataReportService = getMetadataReportService()) != null) { + URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map); + metadataReportService.publishConsumer(consumerURL); + } + // create service proxy + return (T) PROXY_FACTORY.getProxy(invoker); + } + + /** + * Figure out should refer the service in the same JVM from configurations. The default behavior is true + * 1. if injvm is specified, then use it + * 2. then if a url is specified, then assume it's a remote call + * 3. otherwise, check scope parameter + * 4. if scope is not specified but the target service is provided in the same JVM, then prefer to make the local + * call, which is the default behavior + */ + protected boolean shouldJvmRefer(Map map) { + URL tmpUrl = new URL("temp", "localhost", 0, map); + boolean isJvmRefer; + if (isInjvm() == null) { + // if a url is specified, don't do local reference + if (url != null && url.length() > 0) { + isJvmRefer = false; + } else { + // by default, reference local service if there is + isJvmRefer = InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl); + } + } else { + isJvmRefer = isInjvm(); + } + return isJvmRefer; + } + + protected boolean shouldCheck() { + Boolean shouldCheck = isCheck(); + if (shouldCheck == null && getConsumer() != null) { + shouldCheck = getConsumer().isCheck(); + } + if (shouldCheck == null) { + // default true + shouldCheck = true; + } + return shouldCheck; + } + + protected boolean shouldInit() { + Boolean shouldInit = isInit(); + if (shouldInit == null && getConsumer() != null) { + shouldInit = getConsumer().isInit(); + } + if (shouldInit == null) { + // default is false + return false; + } + return shouldInit; + } + + private void checkDefault() { + if (consumer != null) { + return; + } + setConsumer(ConfigManager.getInstance().getDefaultConsumer().orElseGet(() -> { + ConsumerConfig consumerConfig = new ConsumerConfig(); + consumerConfig.refresh(); + return consumerConfig; + })); + } + + private void completeCompoundConfigs() { + if (consumer != null) { + if (application == null) { + setApplication(consumer.getApplication()); + } + if (module == null) { + setModule(consumer.getModule()); + } + if (registries == null) { + setRegistries(consumer.getRegistries()); + } + if (monitor == null) { + setMonitor(consumer.getMonitor()); + } + } + if (module != null) { + if (registries == null) { + setRegistries(module.getRegistries()); + } + if (monitor == null) { + setMonitor(module.getMonitor()); + } + } + if (application != null) { + if (registries == null) { + setRegistries(application.getRegistries()); + } + if (monitor == null) { + setMonitor(application.getMonitor()); + } + } + } + + public Class getInterfaceClass() { + if (interfaceClass != null) { + return interfaceClass; + } + if (isGeneric() + || (getConsumer() != null && getConsumer().isGeneric())) { + return GenericService.class; + } + try { + if (interfaceName != null && interfaceName.length() > 0) { + this.interfaceClass = Class.forName(interfaceName, true, ClassUtils.getClassLoader()); + } + } catch (ClassNotFoundException t) { + throw new IllegalStateException(t.getMessage(), t); + } + return interfaceClass; + } + + /** + * @param interfaceClass + * @see #setInterface(Class) + * @deprecated + */ + @Deprecated + public void setInterfaceClass(Class interfaceClass) { + setInterface(interfaceClass); + } + + public String getInterface() { + return interfaceName; + } + + public void setInterface(String interfaceName) { + this.interfaceName = interfaceName; + if (StringUtils.isEmpty(id)) { + id = interfaceName; + } + } + + public void setInterface(Class interfaceClass) { + if (interfaceClass != null && !interfaceClass.isInterface()) { + throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!"); + } + this.interfaceClass = interfaceClass; + setInterface(interfaceClass == null ? null : interfaceClass.getName()); + } + + public String getClient() { + return client; + } + + public void setClient(String client) { + checkName(Constants.CLIENT_KEY, client); + this.client = client; + } + + @Parameter(excluded = true) + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public List getMethods() { + return methods; + } + + @SuppressWarnings("unchecked") + public void setMethods(List methods) { + this.methods = (List) methods; + } + + public ConsumerConfig getConsumer() { + return consumer; + } + + public void setConsumer(ConsumerConfig consumer) { + ConfigManager.getInstance().addConsumer(consumer); + this.consumer = consumer; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + // just for test + Invoker getInvoker() { + return invoker; + } + + @Override + @Parameter(excluded = true) + public String getPrefix() { + return DUBBO + ".reference." + interfaceName; + } + + private void resolveFile() { + String resolve = System.getProperty(interfaceName); + String resolveFile = null; + if (StringUtils.isEmpty(resolve)) { + resolveFile = System.getProperty("dubbo.resolve.file"); + if (StringUtils.isEmpty(resolveFile)) { + File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties"); + if (userResolveFile.exists()) { + resolveFile = userResolveFile.getAbsolutePath(); + } + } + if (resolveFile != null && resolveFile.length() > 0) { + Properties properties = new Properties(); + try (FileInputStream fis = new FileInputStream(new File(resolveFile))) { + properties.load(fis); + } catch (IOException e) { + throw new IllegalStateException("Failed to load " + resolveFile + ", cause: " + e.getMessage(), e); + } + + resolve = properties.getProperty(interfaceName); + } + } + if (resolve != null && resolve.length() > 0) { + url = resolve; + if (logger.isWarnEnabled()) { + if (resolveFile != null) { + logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service."); + } else { + logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service."); + } + } + } + } + + /** + * Dispatch an {@link Event event} + * + * @param event an {@link Event event} + * @since 2.7.3 + */ + protected void dispatch(Event event) { + eventDispatcher.dispatch(event); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/RegistryConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/RegistryConfig.java index 83f56b1fd4..c9a96cb2a5 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/RegistryConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/RegistryConfig.java @@ -23,11 +23,11 @@ import org.apache.dubbo.remoting.Constants; import java.util.Map; import static org.apache.dubbo.common.constants.CommonConstants.FILE_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PASSWORD_KEY; -import static org.apache.dubbo.config.Constants.REGISTRIES_SUFFIX; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.USERNAME_KEY; +import static org.apache.dubbo.config.Constants.REGISTRIES_SUFFIX; import static org.apache.dubbo.config.Constants.ZOOKEEPER_PROTOCOL; import static org.apache.dubbo.registry.Constants.EXTRA_KEYS_KEY; diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java index 257d2a15ee..cc1138ae66 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java @@ -1,1054 +1,1081 @@ -/* - * 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; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.URLBuilder; -import org.apache.dubbo.common.Version; -import org.apache.dubbo.common.bytecode.Wrapper; -import org.apache.dubbo.common.config.Environment; -import org.apache.dubbo.common.extension.ExtensionLoader; -import org.apache.dubbo.common.utils.ClassUtils; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.common.utils.ConfigUtils; -import org.apache.dubbo.common.utils.NamedThreadFactory; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.config.annotation.Service; -import org.apache.dubbo.config.context.ConfigManager; -import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker; -import org.apache.dubbo.config.support.Parameter; -import org.apache.dubbo.metadata.integration.MetadataReportService; -import org.apache.dubbo.remoting.Constants; -import org.apache.dubbo.rpc.Exporter; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Protocol; -import org.apache.dubbo.rpc.ProxyFactory; -import org.apache.dubbo.rpc.cluster.ConfiguratorFactory; -import org.apache.dubbo.rpc.model.ApplicationModel; -import org.apache.dubbo.rpc.model.ProviderModel; -import org.apache.dubbo.rpc.service.GenericService; -import org.apache.dubbo.rpc.support.ProtocolUtils; - -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO; -import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE; -import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND; -import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY; -import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_BIND; -import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_REGISTRY; -import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; -import static org.apache.dubbo.config.Constants.MULTICAST; -import static org.apache.dubbo.config.Constants.PROTOCOLS_SUFFIX; -import static org.apache.dubbo.rpc.Constants.SCOPE_KEY; -import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL; -import static org.apache.dubbo.config.Constants.SCOPE_NONE; -import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE; -import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; -import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY; -import static org.apache.dubbo.rpc.Constants.GENERIC_KEY; -import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL; -import static org.apache.dubbo.rpc.Constants.PROXY_KEY; -import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; -import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort; -import static org.apache.dubbo.common.utils.NetUtils.getLocalHost; -import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost; -import static org.apache.dubbo.common.utils.NetUtils.isInvalidPort; - -/** - * ServiceConfig - * - * @export - */ -public class ServiceConfig extends AbstractServiceConfig { - - private static final long serialVersionUID = 3033787999037024738L; - - /** - * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios. - * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}. - * For example: - * - *

  • when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample, - * then the protocol is RegistryProtocol
  • - * - *
  • when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then - * the protocol is DubboProtocol
  • - *

    - * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two - * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper - */ - private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); - - /** - * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its - * default implementation - */ - private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); - - /** - * A random port cache, the different protocols who has no port specified have different random port - */ - private static final Map RANDOM_PORT_MAP = new HashMap(); - - /** - * A delayed exposure service timer - */ - private static final ScheduledExecutorService DELAY_EXPORT_EXECUTOR = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true)); - - /** - * The urls of the services exported - */ - private final List urls = new ArrayList(); - - /** - * The exported services - */ - private final List> exporters = new ArrayList>(); - - /** - * The interface name of the exported service - */ - private String interfaceName; - - /** - * The interface class of the exported service - */ - private Class interfaceClass; - - /** - * The reference of the interface implementation - */ - private T ref; - - /** - * The service name - */ - private String path; - - /** - * The method configuration - */ - private List methods; - - /** - * The provider configuration - */ - private ProviderConfig provider; - - /** - * The providerIds - */ - private String providerIds; - - /** - * Whether the provider has been exported - */ - private transient volatile boolean exported; - - /** - * The flag whether a service has unexported ,if the method unexported is invoked, the value is true - */ - private transient volatile boolean unexported; - - /** - * whether it is a GenericService - */ - private volatile String generic; - - public ServiceConfig() { - } - - public ServiceConfig(Service service) { - appendAnnotation(Service.class, service); - setMethods(MethodConfig.constructMethodConfig(service.methods())); - } - - @Deprecated - private static List convertProviderToProtocol(List providers) { - if (CollectionUtils.isEmpty(providers)) { - return null; - } - List protocols = new ArrayList(providers.size()); - for (ProviderConfig provider : providers) { - protocols.add(convertProviderToProtocol(provider)); - } - return protocols; - } - - @Deprecated - private static List convertProtocolToProvider(List protocols) { - if (CollectionUtils.isEmpty(protocols)) { - return null; - } - List providers = new ArrayList(protocols.size()); - for (ProtocolConfig provider : protocols) { - providers.add(convertProtocolToProvider(provider)); - } - return providers; - } - - @Deprecated - private static ProtocolConfig convertProviderToProtocol(ProviderConfig provider) { - ProtocolConfig protocol = new ProtocolConfig(); - protocol.setName(provider.getProtocol().getName()); - protocol.setServer(provider.getServer()); - protocol.setClient(provider.getClient()); - protocol.setCodec(provider.getCodec()); - protocol.setHost(provider.getHost()); - protocol.setPort(provider.getPort()); - protocol.setPath(provider.getPath()); - protocol.setPayload(provider.getPayload()); - protocol.setThreads(provider.getThreads()); - protocol.setParameters(provider.getParameters()); - return protocol; - } - - @Deprecated - private static ProviderConfig convertProtocolToProvider(ProtocolConfig protocol) { - ProviderConfig provider = new ProviderConfig(); - provider.setProtocol(protocol); - provider.setServer(protocol.getServer()); - provider.setClient(protocol.getClient()); - provider.setCodec(protocol.getCodec()); - provider.setHost(protocol.getHost()); - provider.setPort(protocol.getPort()); - provider.setPath(protocol.getPath()); - provider.setPayload(protocol.getPayload()); - provider.setThreads(protocol.getThreads()); - provider.setParameters(protocol.getParameters()); - return provider; - } - - private static Integer getRandomPort(String protocol) { - protocol = protocol.toLowerCase(); - return RANDOM_PORT_MAP.getOrDefault(protocol, Integer.MIN_VALUE); - } - - private static void putRandomPort(String protocol, Integer port) { - protocol = protocol.toLowerCase(); - if (!RANDOM_PORT_MAP.containsKey(protocol)) { - RANDOM_PORT_MAP.put(protocol, port); - logger.warn("Use random available port(" + port + ") for protocol " + protocol); - } - } - - public URL toUrl() { - return urls.isEmpty() ? null : urls.iterator().next(); - } - - public List toUrls() { - return urls; - } - - @Parameter(excluded = true) - public boolean isExported() { - return exported; - } - - @Parameter(excluded = true) - public boolean isUnexported() { - return unexported; - } - - public void checkAndUpdateSubConfigs() { - // Use default configs defined explicitly on global configs - completeCompoundConfigs(); - // Config Center should always being started first. - startConfigCenter(); - checkDefault(); - checkProtocol(); - checkApplication(); - // if protocol is not injvm checkRegistry - if (!isOnlyInJvm()) { - checkRegistry(); - } - this.refresh(); - checkMetadataReport(); - - if (StringUtils.isEmpty(interfaceName)) { - throw new IllegalStateException(" interface not allow null!"); - } - - if (ref instanceof GenericService) { - interfaceClass = GenericService.class; - if (StringUtils.isEmpty(generic)) { - generic = Boolean.TRUE.toString(); - } - } else { - try { - interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() - .getContextClassLoader()); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e.getMessage(), e); - } - checkInterfaceAndMethods(interfaceClass, methods); - checkRef(); - generic = Boolean.FALSE.toString(); - } - if (local != null) { - if ("true".equals(local)) { - local = interfaceName + "Local"; - } - Class localClass; - try { - localClass = ClassUtils.forNameWithThreadContextClassLoader(local); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e.getMessage(), e); - } - if (!interfaceClass.isAssignableFrom(localClass)) { - throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName); - } - } - if (stub != null) { - if ("true".equals(stub)) { - stub = interfaceName + "Stub"; - } - Class stubClass; - try { - stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub); - } catch (ClassNotFoundException e) { - throw new IllegalStateException(e.getMessage(), e); - } - if (!interfaceClass.isAssignableFrom(stubClass)) { - throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName); - } - } - checkStubAndLocal(interfaceClass); - checkMock(interfaceClass); - } - - /** - * Determine if it is injvm - * - * @return - */ - private boolean isOnlyInJvm() { - return getProtocols().size() == 1 && LOCAL_PROTOCOL.equalsIgnoreCase(getProtocols().get(0).getName()); - } - - public synchronized void export() { - checkAndUpdateSubConfigs(); - - if (!shouldExport()) { - return; - } - - if (shouldDelay()) { - DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); - } else { - doExport(); - } - } - - private boolean shouldExport() { - Boolean export = getExport(); - // default value is true - return export == null ? true : export; - } - - @Override - public Boolean getExport() { - return (export == null && provider != null) ? provider.getExport() : export; - } - - private boolean shouldDelay() { - Integer delay = getDelay(); - return delay != null && delay > 0; - } - - @Override - public Integer getDelay() { - return (delay == null && provider != null) ? provider.getDelay() : delay; - } - - protected synchronized void doExport() { - if (unexported) { - throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); - } - if (exported) { - return; - } - exported = true; - - if (StringUtils.isEmpty(path)) { - path = interfaceName; - } - doExportUrls(); - } - - private void checkRef() { - // reference should not be null, and is the implementation of the given interface - if (ref == null) { - throw new IllegalStateException("ref not allow null!"); - } - if (!interfaceClass.isInstance(ref)) { - throw new IllegalStateException("The class " - + ref.getClass().getName() + " unimplemented interface " - + interfaceClass + "!"); - } - } - - public synchronized void unexport() { - if (!exported) { - return; - } - if (unexported) { - return; - } - if (!exporters.isEmpty()) { - for (Exporter exporter : exporters) { - try { - exporter.unexport(); - } catch (Throwable t) { - logger.warn("Unexpected error occured when unexport " + exporter, t); - } - } - exporters.clear(); - } - unexported = true; - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private void doExportUrls() { - List registryURLs = loadRegistries(true); - for (ProtocolConfig protocolConfig : protocols) { - String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); - ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); - ApplicationModel.initProviderModel(pathKey, providerModel); - doExportUrlsFor1Protocol(protocolConfig, registryURLs); - } - } - - private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) { - String name = protocolConfig.getName(); - if (StringUtils.isEmpty(name)) { - name = DUBBO; - } - - Map map = new HashMap(); - map.put(SIDE_KEY, PROVIDER_SIDE); - - appendRuntimeParameters(map); - appendParameters(map, metrics); - appendParameters(map, application); - appendParameters(map, module); - // remove 'default.' prefix for configs from ProviderConfig - // appendParameters(map, provider, Constants.DEFAULT_KEY); - appendParameters(map, provider); - appendParameters(map, protocolConfig); - appendParameters(map, this); - if (CollectionUtils.isNotEmpty(methods)) { - for (MethodConfig method : methods) { - appendParameters(map, method, method.getName()); - String retryKey = method.getName() + ".retry"; - if (map.containsKey(retryKey)) { - String retryValue = map.remove(retryKey); - if ("false".equals(retryValue)) { - map.put(method.getName() + ".retries", "0"); - } - } - List arguments = method.getArguments(); - if (CollectionUtils.isNotEmpty(arguments)) { - for (ArgumentConfig argument : arguments) { - // convert argument type - if (argument.getType() != null && argument.getType().length() > 0) { - Method[] methods = interfaceClass.getMethods(); - // visit all methods - if (methods != null && methods.length > 0) { - for (int i = 0; i < methods.length; i++) { - String methodName = methods[i].getName(); - // target the method, and get its signature - if (methodName.equals(method.getName())) { - Class[] argtypes = methods[i].getParameterTypes(); - // one callback in the method - if (argument.getIndex() != -1) { - if (argtypes[argument.getIndex()].getName().equals(argument.getType())) { - appendParameters(map, argument, method.getName() + "." + argument.getIndex()); - } else { - throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); - } - } else { - // multiple callbacks in the method - for (int j = 0; j < argtypes.length; j++) { - Class argclazz = argtypes[j]; - if (argclazz.getName().equals(argument.getType())) { - appendParameters(map, argument, method.getName() + "." + j); - if (argument.getIndex() != -1 && argument.getIndex() != j) { - throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); - } - } - } - } - } - } - } - } else if (argument.getIndex() != -1) { - appendParameters(map, argument, method.getName() + "." + argument.getIndex()); - } else { - throw new IllegalArgumentException("Argument config must set index or type attribute.eg: or "); - } - - } - } - } // end of methods for - } - - if (ProtocolUtils.isGeneric(generic)) { - map.put(GENERIC_KEY, generic); - map.put(METHODS_KEY, ANY_VALUE); - } else { - String revision = Version.getVersion(interfaceClass, version); - if (revision != null && revision.length() > 0) { - map.put(REVISION_KEY, revision); - } - - String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); - if (methods.length == 0) { - logger.warn("No method found in service interface " + interfaceClass.getName()); - map.put(METHODS_KEY, ANY_VALUE); - } else { - map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ",")); - } - } - if (!ConfigUtils.isEmpty(token)) { - if (ConfigUtils.isDefault(token)) { - map.put(TOKEN_KEY, UUID.randomUUID().toString()); - } else { - map.put(TOKEN_KEY, token); - } - } - // export service - String host = this.findConfigedHosts(protocolConfig, registryURLs, map); - Integer port = this.findConfigedPorts(protocolConfig, name, map); - URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); - - if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) - .hasExtension(url.getProtocol())) { - url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) - .getExtension(url.getProtocol()).getConfigurator(url).configure(url); - } - - String scope = url.getParameter(SCOPE_KEY); - // don't export when none is configured - if (!SCOPE_NONE.equalsIgnoreCase(scope)) { - - // export to local if the config is not remote (export to remote only when config is remote) - if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { - exportLocal(url); - } - // export to remote if the config is not local (export to local only when config is local) - if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { - if (!isOnlyInJvm() && logger.isInfoEnabled()) { - logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); - } - if (CollectionUtils.isNotEmpty(registryURLs)) { - for (URL registryURL : registryURLs) { - //if protocol is only injvm ,not register - if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { - continue; - } - url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); - URL monitorUrl = loadMonitor(registryURL); - if (monitorUrl != null) { - url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); - } - if (logger.isInfoEnabled()) { - logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); - } - - // For providers, this is used to enable custom proxy to generate invoker - String proxy = url.getParameter(PROXY_KEY); - if (StringUtils.isNotEmpty(proxy)) { - registryURL = registryURL.addParameter(PROXY_KEY, proxy); - } - - Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); - DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); - - Exporter exporter = protocol.export(wrapperInvoker); - exporters.add(exporter); - } - } else { - Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); - DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); - - Exporter exporter = protocol.export(wrapperInvoker); - exporters.add(exporter); - } - /** - * @since 2.7.0 - * ServiceData Store - */ - MetadataReportService metadataReportService = null; - if ((metadataReportService = getMetadataReportService()) != null) { - metadataReportService.publishProvider(url); - } - } - } - this.urls.add(url); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - /** - * always export injvm - */ - private void exportLocal(URL url) { - URL local = URLBuilder.from(url) - .setProtocol(LOCAL_PROTOCOL) - .setHost(LOCALHOST_VALUE) - .setPort(0) - .build(); - Exporter exporter = protocol.export( - PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)); - exporters.add(exporter); - logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local); - } - - private Optional getContextPath(ProtocolConfig protocolConfig) { - String contextPath = protocolConfig.getContextpath(); - if (StringUtils.isEmpty(contextPath) && provider != null) { - contextPath = provider.getContextpath(); - } - return Optional.ofNullable(contextPath); - } - - protected Class getServiceClass(T ref) { - return ref.getClass(); - } - - /** - * Register & bind IP address for service provider, can be configured separately. - * Configuration priority: environment variables -> java system properties -> host property in config file -> - * /etc/hosts -> default network address -> first available network address - * - * @param protocolConfig - * @param registryURLs - * @param map - * @return - */ - private String findConfigedHosts(ProtocolConfig protocolConfig, List registryURLs, Map map) { - boolean anyhost = false; - - String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND); - if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) { - throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind); - } - - // if bind ip is not found in environment, keep looking up - if (StringUtils.isEmpty(hostToBind)) { - hostToBind = protocolConfig.getHost(); - if (provider != null && StringUtils.isEmpty(hostToBind)) { - hostToBind = provider.getHost(); - } - if (isInvalidLocalHost(hostToBind)) { - anyhost = true; - try { - hostToBind = InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - logger.warn(e.getMessage(), e); - } - if (isInvalidLocalHost(hostToBind)) { - if (CollectionUtils.isNotEmpty(registryURLs)) { - for (URL registryURL : registryURLs) { - if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) { - // skip multicast registry since we cannot connect to it via Socket - continue; - } - try (Socket socket = new Socket()) { - SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort()); - socket.connect(addr, 1000); - hostToBind = socket.getLocalAddress().getHostAddress(); - break; - } catch (Exception e) { - logger.warn(e.getMessage(), e); - } - } - } - if (isInvalidLocalHost(hostToBind)) { - hostToBind = getLocalHost(); - } - } - } - } - - map.put(Constants.BIND_IP_KEY, hostToBind); - - // registry ip is not used for bind ip by default - String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY); - if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) { - throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); - } else if (StringUtils.isEmpty(hostToRegistry)) { - // bind ip is used as registry ip by default - hostToRegistry = hostToBind; - } - - map.put(ANYHOST_KEY, String.valueOf(anyhost)); - - return hostToRegistry; - } - - /** - * Register port and bind port for the provider, can be configured separately - * Configuration priority: environment variable -> java system properties -> port property in protocol config file - * -> protocol default port - * - * @param protocolConfig - * @param name - * @return - */ - private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map map) { - Integer portToBind = null; - - // parse bind port from environment - String port = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_BIND); - portToBind = parsePort(port); - - // if there's no bind port found from environment, keep looking up. - if (portToBind == null) { - portToBind = protocolConfig.getPort(); - if (provider != null && (portToBind == null || portToBind == 0)) { - portToBind = provider.getPort(); - } - final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort(); - if (portToBind == null || portToBind == 0) { - portToBind = defaultPort; - } - if (portToBind == null || portToBind <= 0) { - portToBind = getRandomPort(name); - if (portToBind == null || portToBind < 0) { - portToBind = getAvailablePort(defaultPort); - putRandomPort(name, portToBind); - } - } - } - - // save bind port, used as url's key later - map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind)); - - // registry port, not used as bind port by default - String portToRegistryStr = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_REGISTRY); - Integer portToRegistry = parsePort(portToRegistryStr); - if (portToRegistry == null) { - portToRegistry = portToBind; - } - - return portToRegistry; - } - - private Integer parsePort(String configPort) { - Integer port = null; - if (configPort != null && configPort.length() > 0) { - try { - Integer intPort = Integer.parseInt(configPort); - if (isInvalidPort(intPort)) { - throw new IllegalArgumentException("Specified invalid port from env value:" + configPort); - } - port = intPort; - } catch (Exception e) { - throw new IllegalArgumentException("Specified invalid port from env value:" + configPort); - } - } - return port; - } - - private String getValueFromConfig(ProtocolConfig protocolConfig, String key) { - String protocolPrefix = protocolConfig.getName().toUpperCase() + "_"; - String port = ConfigUtils.getSystemProperty(protocolPrefix + key); - if (StringUtils.isEmpty(port)) { - port = ConfigUtils.getSystemProperty(key); - } - return port; - } - - private void completeCompoundConfigs() { - if (provider != null) { - if (application == null) { - setApplication(provider.getApplication()); - } - if (module == null) { - setModule(provider.getModule()); - } - if (registries == null) { - setRegistries(provider.getRegistries()); - } - if (monitor == null) { - setMonitor(provider.getMonitor()); - } - if (protocols == null) { - setProtocols(provider.getProtocols()); - } - if (configCenter == null) { - setConfigCenter(provider.getConfigCenter()); - } - } - if (module != null) { - if (registries == null) { - setRegistries(module.getRegistries()); - } - if (monitor == null) { - setMonitor(module.getMonitor()); - } - } - if (application != null) { - if (registries == null) { - setRegistries(application.getRegistries()); - } - if (monitor == null) { - setMonitor(application.getMonitor()); - } - } - } - - private void checkDefault() { - createProviderIfAbsent(); - } - - private void createProviderIfAbsent() { - if (provider != null) { - return; - } - setProvider( - ConfigManager.getInstance() - .getDefaultProvider() - .orElseGet(() -> { - ProviderConfig providerConfig = new ProviderConfig(); - providerConfig.refresh(); - return providerConfig; - }) - ); - } - - private void checkProtocol() { - if (CollectionUtils.isEmpty(protocols) && provider != null) { - setProtocols(provider.getProtocols()); - } - convertProtocolIdsToProtocols(); - } - - private void convertProtocolIdsToProtocols() { - if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) { - List configedProtocols = new ArrayList<>(); - configedProtocols.addAll(getSubProperties(Environment.getInstance() - .getExternalConfigurationMap(), PROTOCOLS_SUFFIX)); - configedProtocols.addAll(getSubProperties(Environment.getInstance() - .getAppExternalConfigurationMap(), PROTOCOLS_SUFFIX)); - - protocolIds = String.join(",", configedProtocols); - } - - if (StringUtils.isEmpty(protocolIds)) { - if (CollectionUtils.isEmpty(protocols)) { - setProtocols( - ConfigManager.getInstance().getDefaultProtocols() - .filter(CollectionUtils::isNotEmpty) - .orElseGet(() -> { - ProtocolConfig protocolConfig = new ProtocolConfig(); - protocolConfig.refresh(); - return new ArrayList<>(Arrays.asList(protocolConfig)); - }) - ); - } - } else { - String[] arr = COMMA_SPLIT_PATTERN.split(protocolIds); - List tmpProtocols = CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>(); - Arrays.stream(arr).forEach(id -> { - if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) { - tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> { - ProtocolConfig protocolConfig = new ProtocolConfig(); - protocolConfig.setId(id); - protocolConfig.refresh(); - return protocolConfig; - })); - } - }); - if (tmpProtocols.size() > arr.length) { - throw new IllegalStateException("Too much protocols found, the protocols comply to this service are :" + protocolIds + " but got " + protocols - .size() + " registries!"); - } - setProtocols(tmpProtocols); - } - } - - public Class getInterfaceClass() { - if (interfaceClass != null) { - return interfaceClass; - } - if (ref instanceof GenericService) { - return GenericService.class; - } - try { - if (interfaceName != null && interfaceName.length() > 0) { - this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() - .getContextClassLoader()); - } - } catch (ClassNotFoundException t) { - throw new IllegalStateException(t.getMessage(), t); - } - return interfaceClass; - } - - /** - * @param interfaceClass - * @see #setInterface(Class) - * @deprecated - */ - public void setInterfaceClass(Class interfaceClass) { - setInterface(interfaceClass); - } - - public String getInterface() { - return interfaceName; - } - - public void setInterface(Class interfaceClass) { - if (interfaceClass != null && !interfaceClass.isInterface()) { - throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!"); - } - this.interfaceClass = interfaceClass; - setInterface(interfaceClass == null ? null : interfaceClass.getName()); - } - - public void setInterface(String interfaceName) { - this.interfaceName = interfaceName; - if (StringUtils.isEmpty(id)) { - id = interfaceName; - } - } - - public T getRef() { - return ref; - } - - public void setRef(T ref) { - this.ref = ref; - } - - @Parameter(excluded = true) - public String getPath() { - return path; - } - - public void setPath(String path) { - checkPathName(PATH_KEY, path); - this.path = path; - } - - public List getMethods() { - return methods; - } - - // ======== Deprecated ======== - - @SuppressWarnings("unchecked") - public void setMethods(List methods) { - this.methods = (List) methods; - } - - public ProviderConfig getProvider() { - return provider; - } - - public void setProvider(ProviderConfig provider) { - ConfigManager.getInstance().addProvider(provider); - this.provider = provider; - } - - @Parameter(excluded = true) - public String getProviderIds() { - return providerIds; - } - - public void setProviderIds(String providerIds) { - this.providerIds = providerIds; - } - - public String getGeneric() { - return generic; - } - - public void setGeneric(String generic) { - if (StringUtils.isEmpty(generic)) { - return; - } - if (ProtocolUtils.isGeneric(generic)) { - this.generic = generic; - } else { - throw new IllegalArgumentException("Unsupported generic type " + generic); - } - } - - @Override - public void setMock(Boolean 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"); - } - - public List getExportedUrls() { - return urls; - } - - /** - * @deprecated Replace to getProtocols() - */ - @Deprecated - public List getProviders() { - return convertProtocolToProvider(protocols); - } - - /** - * @deprecated Replace to setProtocols() - */ - @Deprecated - public void setProviders(List providers) { - this.protocols = convertProviderToProtocol(providers); - } - - @Override - @Parameter(excluded = true) - public String getPrefix() { - return DUBBO + ".service." + interfaceName; - } -} + +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.URLBuilder; +import org.apache.dubbo.common.Version; +import org.apache.dubbo.common.bytecode.Wrapper; +import org.apache.dubbo.common.config.Environment; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.ClassUtils; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.ConfigUtils; +import org.apache.dubbo.common.utils.NamedThreadFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.config.annotation.Service; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.event.ServiceConfigExportedEvent; +import org.apache.dubbo.config.event.ServiceConfigUnexportedEvent; +import org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker; +import org.apache.dubbo.config.support.Parameter; +import org.apache.dubbo.event.Event; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.metadata.integration.MetadataReportService; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.rpc.Exporter; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.ProxyFactory; +import org.apache.dubbo.rpc.cluster.ConfiguratorFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.ProviderModel; +import org.apache.dubbo.rpc.service.GenericService; +import org.apache.dubbo.rpc.support.ProtocolUtils; + +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_IP_TO_BIND; +import static org.apache.dubbo.common.constants.CommonConstants.LOCALHOST_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE; +import static org.apache.dubbo.common.constants.CommonConstants.REVISION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_KEY; +import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort; +import static org.apache.dubbo.common.utils.NetUtils.getLocalHost; +import static org.apache.dubbo.common.utils.NetUtils.isInvalidLocalHost; +import static org.apache.dubbo.common.utils.NetUtils.isInvalidPort; +import static org.apache.dubbo.config.Constants.DUBBO_IP_TO_REGISTRY; +import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_BIND; +import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_REGISTRY; +import static org.apache.dubbo.config.Constants.MULTICAST; +import static org.apache.dubbo.config.Constants.PROTOCOLS_SUFFIX; +import static org.apache.dubbo.config.Constants.SCOPE_NONE; +import static org.apache.dubbo.rpc.Constants.GENERIC_KEY; +import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL; +import static org.apache.dubbo.rpc.Constants.PROXY_KEY; +import static org.apache.dubbo.rpc.Constants.SCOPE_KEY; +import static org.apache.dubbo.rpc.Constants.SCOPE_LOCAL; +import static org.apache.dubbo.rpc.Constants.SCOPE_REMOTE; +import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; + +/** + * ServiceConfig + * + * @export + */ +public class ServiceConfig extends AbstractServiceConfig { + + private static final long serialVersionUID = 3033787999037024738L; + + /** + * The {@link Protocol} implementation with adaptive functionality,it will be different in different scenarios. + * A particular {@link Protocol} implementation is determined by the protocol attribute in the {@link URL}. + * For example: + * + *

  • when the url is registry://224.5.6.7:1234/org.apache.dubbo.registry.RegistryService?application=dubbo-sample, + * then the protocol is RegistryProtocol
  • + * + *
  • when the url is dubbo://224.5.6.7:1234/org.apache.dubbo.config.api.DemoService?application=dubbo-sample, then + * the protocol is DubboProtocol
  • + *

    + * Actually,when the {@link ExtensionLoader} init the {@link Protocol} instants,it will automatically wraps two + * layers, and eventually will get a ProtocolFilterWrapper or ProtocolListenerWrapper + */ + private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); + + /** + * A {@link ProxyFactory} implementation that will generate a exported service proxy,the JavassistProxyFactory is its + * default implementation + */ + private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + + /** + * A random port cache, the different protocols who has no port specified have different random port + */ + private static final Map RANDOM_PORT_MAP = new HashMap(); + + /** + * A delayed exposure service timer + */ + private static final ScheduledExecutorService DELAY_EXPORT_EXECUTOR = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("DubboServiceDelayExporter", true)); + + /** + * The urls of the services exported + */ + private final List urls = new ArrayList(); + + /** + * The exported services + */ + private final List> exporters = new ArrayList>(); + + /** + * The interface name of the exported service + */ + private String interfaceName; + + /** + * The interface class of the exported service + */ + private Class interfaceClass; + + /** + * The reference of the interface implementation + */ + private T ref; + + /** + * The service name + */ + private String path; + + /** + * The method configuration + */ + private List methods; + + /** + * The provider configuration + */ + private ProviderConfig provider; + + /** + * The providerIds + */ + private String providerIds; + + /** + * Whether the provider has been exported + */ + private transient volatile boolean exported; + + /** + * The flag whether a service has unexported ,if the method unexported is invoked, the value is true + */ + private transient volatile boolean unexported; + + /** + * whether it is a GenericService + */ + private volatile String generic; + + /** + * @since 2.7.3 + */ + private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + + + public ServiceConfig() { + } + + public ServiceConfig(Service service) { + appendAnnotation(Service.class, service); + setMethods(MethodConfig.constructMethodConfig(service.methods())); + } + + @Deprecated + private static List convertProviderToProtocol(List providers) { + if (CollectionUtils.isEmpty(providers)) { + return null; + } + List protocols = new ArrayList(providers.size()); + for (ProviderConfig provider : providers) { + protocols.add(convertProviderToProtocol(provider)); + } + return protocols; + } + + @Deprecated + private static List convertProtocolToProvider(List protocols) { + if (CollectionUtils.isEmpty(protocols)) { + return null; + } + List providers = new ArrayList(protocols.size()); + for (ProtocolConfig provider : protocols) { + providers.add(convertProtocolToProvider(provider)); + } + return providers; + } + + @Deprecated + private static ProtocolConfig convertProviderToProtocol(ProviderConfig provider) { + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setName(provider.getProtocol().getName()); + protocol.setServer(provider.getServer()); + protocol.setClient(provider.getClient()); + protocol.setCodec(provider.getCodec()); + protocol.setHost(provider.getHost()); + protocol.setPort(provider.getPort()); + protocol.setPath(provider.getPath()); + protocol.setPayload(provider.getPayload()); + protocol.setThreads(provider.getThreads()); + protocol.setParameters(provider.getParameters()); + return protocol; + } + + @Deprecated + private static ProviderConfig convertProtocolToProvider(ProtocolConfig protocol) { + ProviderConfig provider = new ProviderConfig(); + provider.setProtocol(protocol); + provider.setServer(protocol.getServer()); + provider.setClient(protocol.getClient()); + provider.setCodec(protocol.getCodec()); + provider.setHost(protocol.getHost()); + provider.setPort(protocol.getPort()); + provider.setPath(protocol.getPath()); + provider.setPayload(protocol.getPayload()); + provider.setThreads(protocol.getThreads()); + provider.setParameters(protocol.getParameters()); + return provider; + } + + private static Integer getRandomPort(String protocol) { + protocol = protocol.toLowerCase(); + return RANDOM_PORT_MAP.getOrDefault(protocol, Integer.MIN_VALUE); + } + + private static void putRandomPort(String protocol, Integer port) { + protocol = protocol.toLowerCase(); + if (!RANDOM_PORT_MAP.containsKey(protocol)) { + RANDOM_PORT_MAP.put(protocol, port); + logger.warn("Use random available port(" + port + ") for protocol " + protocol); + } + } + + public URL toUrl() { + return urls.isEmpty() ? null : urls.iterator().next(); + } + + public List toUrls() { + return urls; + } + + @Parameter(excluded = true) + public boolean isExported() { + return exported; + } + + @Parameter(excluded = true) + public boolean isUnexported() { + return unexported; + } + + public void checkAndUpdateSubConfigs() { + // Use default configs defined explicitly on global configs + completeCompoundConfigs(); + // Config Center should always being started first. + startConfigCenter(); + checkDefault(); + checkProtocol(); + checkApplication(); + // if protocol is not injvm checkRegistry + if (!isOnlyInJvm()) { + checkRegistry(); + } + this.refresh(); + checkMetadataReport(); + + if (StringUtils.isEmpty(interfaceName)) { + throw new IllegalStateException(" interface not allow null!"); + } + + if (ref instanceof GenericService) { + interfaceClass = GenericService.class; + if (StringUtils.isEmpty(generic)) { + generic = Boolean.TRUE.toString(); + } + } else { + try { + interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() + .getContextClassLoader()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + checkInterfaceAndMethods(interfaceClass, methods); + checkRef(); + generic = Boolean.FALSE.toString(); + } + if (local != null) { + if ("true".equals(local)) { + local = interfaceName + "Local"; + } + Class localClass; + try { + localClass = ClassUtils.forNameWithThreadContextClassLoader(local); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + if (!interfaceClass.isAssignableFrom(localClass)) { + throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName); + } + } + if (stub != null) { + if ("true".equals(stub)) { + stub = interfaceName + "Stub"; + } + Class stubClass; + try { + stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e.getMessage(), e); + } + if (!interfaceClass.isAssignableFrom(stubClass)) { + throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName); + } + } + checkStubAndLocal(interfaceClass); + checkMock(interfaceClass); + } + + /** + * Determine if it is injvm + * + * @return + */ + private boolean isOnlyInJvm() { + return getProtocols().size() == 1 && LOCAL_PROTOCOL.equalsIgnoreCase(getProtocols().get(0).getName()); + } + + public synchronized void export() { + checkAndUpdateSubConfigs(); + + if (!shouldExport()) { + return; + } + + if (shouldDelay()) { + DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS); + } else { + doExport(); + } + } + + private boolean shouldExport() { + Boolean export = getExport(); + // default value is true + return export == null ? true : export; + } + + @Override + public Boolean getExport() { + return (export == null && provider != null) ? provider.getExport() : export; + } + + private boolean shouldDelay() { + Integer delay = getDelay(); + return delay != null && delay > 0; + } + + @Override + public Integer getDelay() { + return (delay == null && provider != null) ? provider.getDelay() : delay; + } + + protected synchronized void doExport() { + if (unexported) { + throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); + } + if (exported) { + return; + } + exported = true; + + if (StringUtils.isEmpty(path)) { + path = interfaceName; + } + doExportUrls(); + + // dispatch a ServiceConfigExportedEvent since 2.7.3 + dispatch(new ServiceConfigExportedEvent(this)); + } + + private void checkRef() { + // reference should not be null, and is the implementation of the given interface + if (ref == null) { + throw new IllegalStateException("ref not allow null!"); + } + if (!interfaceClass.isInstance(ref)) { + throw new IllegalStateException("The class " + + ref.getClass().getName() + " unimplemented interface " + + interfaceClass + "!"); + } + } + + public synchronized void unexport() { + if (!exported) { + return; + } + if (unexported) { + return; + } + if (!exporters.isEmpty()) { + for (Exporter exporter : exporters) { + try { + exporter.unexport(); + } catch (Throwable t) { + logger.warn("Unexpected error occured when unexport " + exporter, t); + } + } + exporters.clear(); + } + unexported = true; + + // dispatch a ServiceConfigUnExportedEvent since 2.7.3 + dispatch(new ServiceConfigUnexportedEvent(this)); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void doExportUrls() { + List registryURLs = loadRegistries(true); + for (ProtocolConfig protocolConfig : protocols) { + String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); + ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); + ApplicationModel.initProviderModel(pathKey, providerModel); + doExportUrlsFor1Protocol(protocolConfig, registryURLs); + } + } + + private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) { + String name = protocolConfig.getName(); + if (StringUtils.isEmpty(name)) { + name = DUBBO; + } + + Map map = new HashMap(); + map.put(SIDE_KEY, PROVIDER_SIDE); + + appendRuntimeParameters(map); + appendParameters(map, metrics); + appendParameters(map, application); + appendParameters(map, module); + // remove 'default.' prefix for configs from ProviderConfig + // appendParameters(map, provider, Constants.DEFAULT_KEY); + appendParameters(map, provider); + appendParameters(map, protocolConfig); + appendParameters(map, this); + if (CollectionUtils.isNotEmpty(methods)) { + for (MethodConfig method : methods) { + appendParameters(map, method, method.getName()); + String retryKey = method.getName() + ".retry"; + if (map.containsKey(retryKey)) { + String retryValue = map.remove(retryKey); + if ("false".equals(retryValue)) { + map.put(method.getName() + ".retries", "0"); + } + } + List arguments = method.getArguments(); + if (CollectionUtils.isNotEmpty(arguments)) { + for (ArgumentConfig argument : arguments) { + // convert argument type + if (argument.getType() != null && argument.getType().length() > 0) { + Method[] methods = interfaceClass.getMethods(); + // visit all methods + if (methods != null && methods.length > 0) { + for (int i = 0; i < methods.length; i++) { + String methodName = methods[i].getName(); + // target the method, and get its signature + if (methodName.equals(method.getName())) { + Class[] argtypes = methods[i].getParameterTypes(); + // one callback in the method + if (argument.getIndex() != -1) { + if (argtypes[argument.getIndex()].getName().equals(argument.getType())) { + appendParameters(map, argument, method.getName() + "." + argument.getIndex()); + } else { + throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); + } + } else { + // multiple callbacks in the method + for (int j = 0; j < argtypes.length; j++) { + Class argclazz = argtypes[j]; + if (argclazz.getName().equals(argument.getType())) { + appendParameters(map, argument, method.getName() + "." + j); + if (argument.getIndex() != -1 && argument.getIndex() != j) { + throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType()); + } + } + } + } + } + } + } + } else if (argument.getIndex() != -1) { + appendParameters(map, argument, method.getName() + "." + argument.getIndex()); + } else { + throw new IllegalArgumentException("Argument config must set index or type attribute.eg: or "); + } + + } + } + } // end of methods for + } + + if (ProtocolUtils.isGeneric(generic)) { + map.put(GENERIC_KEY, generic); + map.put(METHODS_KEY, ANY_VALUE); + } else { + String revision = Version.getVersion(interfaceClass, version); + if (revision != null && revision.length() > 0) { + map.put(REVISION_KEY, revision); + } + + String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); + if (methods.length == 0) { + logger.warn("No method found in service interface " + interfaceClass.getName()); + map.put(METHODS_KEY, ANY_VALUE); + } else { + map.put(METHODS_KEY, StringUtils.join(new HashSet(Arrays.asList(methods)), ",")); + } + } + if (!ConfigUtils.isEmpty(token)) { + if (ConfigUtils.isDefault(token)) { + map.put(TOKEN_KEY, UUID.randomUUID().toString()); + } else { + map.put(TOKEN_KEY, token); + } + } + // export service + String host = this.findConfigedHosts(protocolConfig, registryURLs, map); + Integer port = this.findConfigedPorts(protocolConfig, name, map); + URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); + + if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) + .hasExtension(url.getProtocol())) { + url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) + .getExtension(url.getProtocol()).getConfigurator(url).configure(url); + } + + String scope = url.getParameter(SCOPE_KEY); + // don't export when none is configured + if (!SCOPE_NONE.equalsIgnoreCase(scope)) { + + // export to local if the config is not remote (export to remote only when config is remote) + if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) { + exportLocal(url); + } + // export to remote if the config is not local (export to local only when config is local) + if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) { + if (!isOnlyInJvm() && logger.isInfoEnabled()) { + logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); + } + if (CollectionUtils.isNotEmpty(registryURLs)) { + for (URL registryURL : registryURLs) { + //if protocol is only injvm ,not register + if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { + continue; + } + url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY)); + URL monitorUrl = loadMonitor(registryURL); + if (monitorUrl != null) { + url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString()); + } + if (logger.isInfoEnabled()) { + logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); + } + + // For providers, this is used to enable custom proxy to generate invoker + String proxy = url.getParameter(PROXY_KEY); + if (StringUtils.isNotEmpty(proxy)) { + registryURL = registryURL.addParameter(PROXY_KEY, proxy); + } + + Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())); + DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); + + Exporter exporter = protocol.export(wrapperInvoker); + exporters.add(exporter); + } + } else { + Invoker invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url); + DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); + + Exporter exporter = protocol.export(wrapperInvoker); + exporters.add(exporter); + } + /** + * @since 2.7.0 + * ServiceData Store + */ + MetadataReportService metadataReportService = null; + if ((metadataReportService = getMetadataReportService()) != null) { + metadataReportService.publishProvider(url); + } + } + } + this.urls.add(url); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + /** + * always export injvm + */ + private void exportLocal(URL url) { + URL local = URLBuilder.from(url) + .setProtocol(LOCAL_PROTOCOL) + .setHost(LOCALHOST_VALUE) + .setPort(0) + .build(); + Exporter exporter = protocol.export( + PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local)); + exporters.add(exporter); + logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local); + } + + private Optional getContextPath(ProtocolConfig protocolConfig) { + String contextPath = protocolConfig.getContextpath(); + if (StringUtils.isEmpty(contextPath) && provider != null) { + contextPath = provider.getContextpath(); + } + return Optional.ofNullable(contextPath); + } + + protected Class getServiceClass(T ref) { + return ref.getClass(); + } + + /** + * Register & bind IP address for service provider, can be configured separately. + * Configuration priority: environment variables -> java system properties -> host property in config file -> + * /etc/hosts -> default network address -> first available network address + * + * @param protocolConfig + * @param registryURLs + * @param map + * @return + */ + private String findConfigedHosts(ProtocolConfig protocolConfig, List registryURLs, Map map) { + boolean anyhost = false; + + String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND); + if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) { + throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind); + } + + // if bind ip is not found in environment, keep looking up + if (StringUtils.isEmpty(hostToBind)) { + hostToBind = protocolConfig.getHost(); + if (provider != null && StringUtils.isEmpty(hostToBind)) { + hostToBind = provider.getHost(); + } + if (isInvalidLocalHost(hostToBind)) { + anyhost = true; + try { + hostToBind = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + logger.warn(e.getMessage(), e); + } + if (isInvalidLocalHost(hostToBind)) { + if (CollectionUtils.isNotEmpty(registryURLs)) { + for (URL registryURL : registryURLs) { + if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) { + // skip multicast registry since we cannot connect to it via Socket + continue; + } + try (Socket socket = new Socket()) { + SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort()); + socket.connect(addr, 1000); + hostToBind = socket.getLocalAddress().getHostAddress(); + break; + } catch (Exception e) { + logger.warn(e.getMessage(), e); + } + } + } + if (isInvalidLocalHost(hostToBind)) { + hostToBind = getLocalHost(); + } + } + } + } + + map.put(Constants.BIND_IP_KEY, hostToBind); + + // registry ip is not used for bind ip by default + String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY); + if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) { + throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); + } else if (StringUtils.isEmpty(hostToRegistry)) { + // bind ip is used as registry ip by default + hostToRegistry = hostToBind; + } + + map.put(ANYHOST_KEY, String.valueOf(anyhost)); + + return hostToRegistry; + } + + /** + * Register port and bind port for the provider, can be configured separately + * Configuration priority: environment variable -> java system properties -> port property in protocol config file + * -> protocol default port + * + * @param protocolConfig + * @param name + * @return + */ + private Integer findConfigedPorts(ProtocolConfig protocolConfig, String name, Map map) { + Integer portToBind = null; + + // parse bind port from environment + String port = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_BIND); + portToBind = parsePort(port); + + // if there's no bind port found from environment, keep looking up. + if (portToBind == null) { + portToBind = protocolConfig.getPort(); + if (provider != null && (portToBind == null || portToBind == 0)) { + portToBind = provider.getPort(); + } + final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort(); + if (portToBind == null || portToBind == 0) { + portToBind = defaultPort; + } + if (portToBind == null || portToBind <= 0) { + portToBind = getRandomPort(name); + if (portToBind == null || portToBind < 0) { + portToBind = getAvailablePort(defaultPort); + putRandomPort(name, portToBind); + } + } + } + + // save bind port, used as url's key later + map.put(Constants.BIND_PORT_KEY, String.valueOf(portToBind)); + + // registry port, not used as bind port by default + String portToRegistryStr = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_REGISTRY); + Integer portToRegistry = parsePort(portToRegistryStr); + if (portToRegistry == null) { + portToRegistry = portToBind; + } + + return portToRegistry; + } + + private Integer parsePort(String configPort) { + Integer port = null; + if (configPort != null && configPort.length() > 0) { + try { + Integer intPort = Integer.parseInt(configPort); + if (isInvalidPort(intPort)) { + throw new IllegalArgumentException("Specified invalid port from env value:" + configPort); + } + port = intPort; + } catch (Exception e) { + throw new IllegalArgumentException("Specified invalid port from env value:" + configPort); + } + } + return port; + } + + private String getValueFromConfig(ProtocolConfig protocolConfig, String key) { + String protocolPrefix = protocolConfig.getName().toUpperCase() + "_"; + String port = ConfigUtils.getSystemProperty(protocolPrefix + key); + if (StringUtils.isEmpty(port)) { + port = ConfigUtils.getSystemProperty(key); + } + return port; + } + + private void completeCompoundConfigs() { + if (provider != null) { + if (application == null) { + setApplication(provider.getApplication()); + } + if (module == null) { + setModule(provider.getModule()); + } + if (registries == null) { + setRegistries(provider.getRegistries()); + } + if (monitor == null) { + setMonitor(provider.getMonitor()); + } + if (protocols == null) { + setProtocols(provider.getProtocols()); + } + if (configCenter == null) { + setConfigCenter(provider.getConfigCenter()); + } + } + if (module != null) { + if (registries == null) { + setRegistries(module.getRegistries()); + } + if (monitor == null) { + setMonitor(module.getMonitor()); + } + } + if (application != null) { + if (registries == null) { + setRegistries(application.getRegistries()); + } + if (monitor == null) { + setMonitor(application.getMonitor()); + } + } + } + + private void checkDefault() { + createProviderIfAbsent(); + } + + private void createProviderIfAbsent() { + if (provider != null) { + return; + } + setProvider( + ConfigManager.getInstance() + .getDefaultProvider() + .orElseGet(() -> { + ProviderConfig providerConfig = new ProviderConfig(); + providerConfig.refresh(); + return providerConfig; + }) + ); + } + + private void checkProtocol() { + if (CollectionUtils.isEmpty(protocols) && provider != null) { + setProtocols(provider.getProtocols()); + } + convertProtocolIdsToProtocols(); + } + + private void convertProtocolIdsToProtocols() { + if (StringUtils.isEmpty(protocolIds) && CollectionUtils.isEmpty(protocols)) { + List configedProtocols = new ArrayList<>(); + configedProtocols.addAll(getSubProperties(Environment.getInstance() + .getExternalConfigurationMap(), PROTOCOLS_SUFFIX)); + configedProtocols.addAll(getSubProperties(Environment.getInstance() + .getAppExternalConfigurationMap(), PROTOCOLS_SUFFIX)); + + protocolIds = String.join(",", configedProtocols); + } + + if (StringUtils.isEmpty(protocolIds)) { + if (CollectionUtils.isEmpty(protocols)) { + setProtocols( + ConfigManager.getInstance().getDefaultProtocols() + .filter(CollectionUtils::isNotEmpty) + .orElseGet(() -> { + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.refresh(); + return new ArrayList<>(Arrays.asList(protocolConfig)); + }) + ); + } + } else { + String[] arr = COMMA_SPLIT_PATTERN.split(protocolIds); + List tmpProtocols = CollectionUtils.isNotEmpty(protocols) ? protocols : new ArrayList<>(); + Arrays.stream(arr).forEach(id -> { + if (tmpProtocols.stream().noneMatch(prot -> prot.getId().equals(id))) { + tmpProtocols.add(ConfigManager.getInstance().getProtocol(id).orElseGet(() -> { + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setId(id); + protocolConfig.refresh(); + return protocolConfig; + })); + } + }); + if (tmpProtocols.size() > arr.length) { + throw new IllegalStateException("Too much protocols found, the protocols comply to this service are :" + protocolIds + " but got " + protocols + .size() + " registries!"); + } + setProtocols(tmpProtocols); + } + } + + public Class getInterfaceClass() { + if (interfaceClass != null) { + return interfaceClass; + } + if (ref instanceof GenericService) { + return GenericService.class; + } + try { + if (interfaceName != null && interfaceName.length() > 0) { + this.interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() + .getContextClassLoader()); + } + } catch (ClassNotFoundException t) { + throw new IllegalStateException(t.getMessage(), t); + } + return interfaceClass; + } + + /** + * @param interfaceClass + * @see #setInterface(Class) + * @deprecated + */ + public void setInterfaceClass(Class interfaceClass) { + setInterface(interfaceClass); + } + + public String getInterface() { + return interfaceName; + } + + public void setInterface(Class interfaceClass) { + if (interfaceClass != null && !interfaceClass.isInterface()) { + throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!"); + } + this.interfaceClass = interfaceClass; + setInterface(interfaceClass == null ? null : interfaceClass.getName()); + } + + public void setInterface(String interfaceName) { + this.interfaceName = interfaceName; + if (StringUtils.isEmpty(id)) { + id = interfaceName; + } + } + + public T getRef() { + return ref; + } + + public void setRef(T ref) { + this.ref = ref; + } + + @Parameter(excluded = true) + public String getPath() { + return path; + } + + public void setPath(String path) { + checkPathName(PATH_KEY, path); + this.path = path; + } + + public List getMethods() { + return methods; + } + + // ======== Deprecated ======== + + @SuppressWarnings("unchecked") + public void setMethods(List methods) { + this.methods = (List) methods; + } + + public ProviderConfig getProvider() { + return provider; + } + + public void setProvider(ProviderConfig provider) { + ConfigManager.getInstance().addProvider(provider); + this.provider = provider; + } + + @Parameter(excluded = true) + public String getProviderIds() { + return providerIds; + } + + public void setProviderIds(String providerIds) { + this.providerIds = providerIds; + } + + public String getGeneric() { + return generic; + } + + public void setGeneric(String generic) { + if (StringUtils.isEmpty(generic)) { + return; + } + if (ProtocolUtils.isGeneric(generic)) { + this.generic = generic; + } else { + throw new IllegalArgumentException("Unsupported generic type " + generic); + } + } + + @Override + public void setMock(Boolean 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"); + } + + public List getExportedUrls() { + return urls; + } + + /** + * @deprecated Replace to getProtocols() + */ + @Deprecated + public List getProviders() { + return convertProtocolToProvider(protocols); + } + + /** + * @deprecated Replace to setProtocols() + */ + @Deprecated + public void setProviders(List providers) { + this.protocols = convertProviderToProtocol(providers); + } + + @Override + @Parameter(excluded = true) + public String getPrefix() { + return DUBBO + ".service." + interfaceName; + } + + /** + * Dispatch an {@link Event event} + * + * @param event an {@link Event event} + * @since 2.7.3 + */ + protected void dispatch(Event event) { + eventDispatcher.dispatch(event); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/AbstractBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/AbstractBuilder.java index d1e33c607b..12d09bf05c 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/AbstractBuilder.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/AbstractBuilder.java @@ -25,9 +25,11 @@ import java.util.Map; /** * AbstractBuilder * + * @param The type of {@link AbstractConfig Config} + * @param The type of {@link AbstractBuilder Builder} * @since 2.7 */ -public abstract class AbstractBuilder { +public abstract class AbstractBuilder { /** * The config id */ @@ -62,7 +64,7 @@ public abstract class AbstractBuilder extends AbstractReferenceBuilder id(String id) { + return super.id(id); + } + public ReferenceBuilder interfaceName(String interfaceName) { this.interfaceName = interfaceName; return getThis(); diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/RegistryBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/RegistryBuilder.java index f64e595f37..616e6af09a 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/RegistryBuilder.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/builders/RegistryBuilder.java @@ -16,10 +16,10 @@ */ package org.apache.dubbo.config.builders; -import java.util.Map; - import org.apache.dubbo.config.RegistryConfig; +import java.util.Map; + /** * This is a builder for build {@link RegistryConfig}. * @@ -134,6 +134,10 @@ public class RegistryBuilder extends AbstractBuilder extends AbstractServiceBuilder interfaceName(String interfaceName) { this.interfaceName = interfaceName; return getThis(); diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/DubboServiceDestroyedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/DubboServiceDestroyedEvent.java new file mode 100644 index 0000000000..a3384aac8d --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/DubboServiceDestroyedEvent.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.event; + +import org.apache.dubbo.event.Event; + +/** + * An {@link Event Dubbo event} when the Dubbo service is about to be destroyed. + * + * @see Event + * @since 2.7.3 + */ +public class DubboServiceDestroyedEvent extends Event { + + public DubboServiceDestroyedEvent(Object source) { + super(source); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.java new file mode 100644 index 0000000000..8f07e51e51 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigDestroyedEvent.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.config.event; + +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.annotation.Reference; +import org.apache.dubbo.event.Event; + +/** + * The {@link ReferenceConfig Dubbo service ReferenceConfig} destroyed {@link Event event} + * + * @see Reference + * @see ReferenceConfig#destroy() + * @see Event + * @since 2.7.3 + */ +public class ReferenceConfigDestroyedEvent extends Event { + + public ReferenceConfigDestroyedEvent(ReferenceConfig referenceConfig) { + super(referenceConfig); + } + + public ReferenceConfig getReferenceConfig() { + return (ReferenceConfig) getSource(); + } + +} \ No newline at end of file diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.java new file mode 100644 index 0000000000..359c8bce60 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ReferenceConfigInitializedEvent.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.config.event; + +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.annotation.Reference; +import org.apache.dubbo.event.Event; +import org.apache.dubbo.rpc.Invoker; + +/** + * The {@link ReferenceConfig Dubbo service ReferenceConfig} initialized {@link Event event} + * + * @see Reference + * @see ReferenceConfig#get() + * @see Event + * @since 2.7.3 + */ +public class ReferenceConfigInitializedEvent extends Event { + + private final Invoker invoker; + + public ReferenceConfigInitializedEvent(ReferenceConfig referenceConfig, Invoker invoker) { + super(referenceConfig); + this.invoker = invoker; + } + + public ReferenceConfig getReferenceConfig() { + return (ReferenceConfig) getSource(); + } + + public Invoker getInvoker() { + return invoker; + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java new file mode 100644 index 0000000000..ebf15f359d --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigExportedEvent.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.event; + +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.event.Event; + +/** + * {@link ServiceConfig} event post-{@link ServiceConfig#export() export} + * + * @since 2.7.3 + */ +public class ServiceConfigExportedEvent extends Event { + + public ServiceConfigExportedEvent(ServiceConfig source) { + super(source); + } + + public ServiceConfig getServiceConfig() { + return (ServiceConfig) getSource(); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java new file mode 100644 index 0000000000..15bd3e567e --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/ServiceConfigUnexportedEvent.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.event; + +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.event.Event; + +/** + * {@link ServiceConfig} event post-{@link ServiceConfig#unexport() unexport} + * + * @since 2.7.3 + */ +public class ServiceConfigUnexportedEvent extends Event { + + public ServiceConfigUnexportedEvent(ServiceConfig source) { + super(source); + } + + public ServiceConfig getServiceConfig() { + return (ServiceConfig) getSource(); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/LoggingEventListener.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/LoggingEventListener.java new file mode 100644 index 0000000000..661c3d7e25 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/LoggingEventListener.java @@ -0,0 +1,51 @@ +/* + * 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.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.config.event.DubboServiceDestroyedEvent; +import org.apache.dubbo.config.event.ServiceConfigExportedEvent; +import org.apache.dubbo.event.Event; +import org.apache.dubbo.event.GenericEventListener; + +import static java.lang.String.format; + +/** + * A listener for logging the {@link Event Dubbo event} + * + * @see ServiceConfigExportedEvent + * @since 2.7.3 + */ +public class LoggingEventListener extends GenericEventListener { + + private static final String NAME = "Dubbo Service"; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public void onEvent(DubboServiceDestroyedEvent event) { + if (logger.isInfoEnabled()) { + logger.info(NAME + " has been destroyed."); + } + } + + private void debug(String pattern, Object... args) { + if (logger.isDebugEnabled()) { + logger.debug(format(pattern, args)); + } + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java new file mode 100644 index 0000000000..bef416ad51 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListener.java @@ -0,0 +1,55 @@ +/* + * 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.ServiceConfig; +import org.apache.dubbo.config.event.ServiceConfigExportedEvent; +import org.apache.dubbo.event.EventListener; +import org.apache.dubbo.metadata.ServiceNameMapping; + +import java.util.List; + +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.metadata.ServiceNameMapping.getDefaultExtension; + +/** + * An {@link EventListener event listener} for mapping {@link ServiceConfig#getExportedUrls() the exported Dubbo + * service inerface} to its service name + * + * @see ServiceNameMapping + * @see ServiceConfig#getExportedUrls() + * @since 2.7.3 + */ +public class ServiceNameMappingListener implements EventListener { + + private final ServiceNameMapping serviceNameMapping = getDefaultExtension(); + + @Override + public void onEvent(ServiceConfigExportedEvent event) { + ServiceConfig serviceConfig = event.getServiceConfig(); + List exportedURLs = serviceConfig.getExportedUrls(); + exportedURLs.forEach(url -> { + String serviceInterface = url.getServiceInterface(); + String group = url.getParameter(GROUP_KEY); + String version = url.getParameter(VERSION_KEY); + String protocol = url.getProtocol(); + serviceNameMapping.map(serviceInterface, group, version, protocol); + }); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java new file mode 100644 index 0000000000..ac36f9e477 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporter.java @@ -0,0 +1,131 @@ +/* + * 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.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.config.AbstractConfig; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.metadata.LocalMetadataService; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.metadata.MetadataServiceExporter; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import static java.util.Collections.unmodifiableList; + +/** + * {@link MetadataServiceExporter} implementation based on {@link AbstractConfig Dubbo configurations}, the clients + * should make sure the {@link ApplicationConfig}, {@link RegistryConfig} and {@link ProtocolConfig} are ready before + * {@link #export()}. + *

    + * Typically, do not worry about their ready status, because they are initialized before + * any {@link ServiceConfig} exports, or The Dubbo export will be failed. + * + * @see MetadataServiceExporter + * @see ServiceConfig + * @see ConfigManager + * @since 2.7.3 + */ +public class ConfigurableMetadataServiceExporter implements MetadataServiceExporter { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private volatile ServiceConfig serviceConfig; + + private ApplicationConfig applicationConfig; + + private List registries = new LinkedList<>(); + + private List protocols = new LinkedList<>(); + + public void setApplicationConfig(ApplicationConfig applicationConfig) { + this.applicationConfig = applicationConfig; + } + + public void setRegistries(Collection registries) { + this.registries.clear(); + this.registries.addAll(registries); + } + + public void setProtocols(Collection protocols) { + this.protocols.clear(); + this.protocols.addAll(protocols); + } + + @Override + public List export() { + + if (!isExported()) { + + LocalMetadataService metadataService = LocalMetadataService.getDefaultExtension(); + + ServiceConfig serviceConfig = new ServiceConfig<>(); + serviceConfig.setApplication(applicationConfig); + serviceConfig.setRegistries(registries); + serviceConfig.setProtocols(protocols); + serviceConfig.setInterface(MetadataService.class); + serviceConfig.setRef(metadataService); + serviceConfig.setGroup(getApplicationConfig().getName()); + serviceConfig.setVersion(metadataService.version()); + + // export + serviceConfig.export(); + + if (logger.isInfoEnabled()) { + logger.info("The MetadataService exports urls : " + serviceConfig.getExportedUrls()); + } + + this.serviceConfig = serviceConfig; + } else { + if (logger.isWarnEnabled()) { + logger.warn("The MetadataService has been exported : " + serviceConfig.getExportedUrls()); + } + } + return serviceConfig.getExportedUrls(); + } + + @Override + public void unexport() { + if (isExported()) { + serviceConfig.unexport(); + } + } + + private List getProtocols() { + return unmodifiableList(protocols); + } + + private List getRegistries() { + return unmodifiableList(registries); + } + + private ApplicationConfig getApplicationConfig() { + return applicationConfig; + } + + private boolean isExported() { + return serviceConfig != null && serviceConfig.isExported(); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstancePortCustomizer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstancePortCustomizer.java new file mode 100644 index 0000000000..c9ef9b9f1c --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/metadata/ServiceInstancePortCustomizer.java @@ -0,0 +1,53 @@ +/* + * 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.config.context.ConfigManager; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstanceCustomizer; + +/** + * The {@link ServiceInstanceCustomizer} to customize the {@link ServiceInstance#getPort() port} of service instance. + * + * @since 2.7.3 + */ +public class ServiceInstancePortCustomizer implements ServiceInstanceCustomizer { + + @Override + public void customize(ServiceInstance serviceInstance) { + + if (serviceInstance.getPort() != null + || serviceInstance.getPort().intValue() < 1) { + return; + } + + ConfigManager.getInstance() + .getProtocols() + .values() + .stream() + .findFirst() + .ifPresent(protocolConfig -> { + if (serviceInstance instanceof DefaultServiceInstance) { + DefaultServiceInstance instance = (DefaultServiceInstance) serviceInstance; + if (protocolConfig.getPort() != null) { + instance.setPort(protocolConfig.getPort()); + } + } + }); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener new file mode 100644 index 0000000000..d77cfb49ec --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener @@ -0,0 +1,2 @@ +org.apache.dubbo.config.event.listener.ServiceNameMappingListener +org.apache.dubbo.config.event.listener.LoggingEventListener \ No newline at end of file diff --git a/dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer new file mode 100644 index 0000000000..e708dc0491 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer @@ -0,0 +1 @@ +org.apache.dubbo.config.metadata.ServiceInstancePortCustomizer \ No newline at end of file diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboConsumerBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboConsumerBootstrap.java new file mode 100644 index 0000000000..b823936812 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboConsumerBootstrap.java @@ -0,0 +1,63 @@ +/* + * 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; + +import org.apache.dubbo.config.api.DemoService; + +import java.io.IOException; + +/** + * Dubbo Consumer Bootstrap + * + * @since 2.7.3 + */ +public class DubboConsumerBootstrap { + + public static void main(String[] args) throws IOException, InterruptedException { + + ApplicationConfig application = new ApplicationConfig(); + application.setName("dubbo-consumer-demo"); + + // 连接注册中心配置 + RegistryConfig registry = new RegistryConfig(); + registry.setAddress("zookeeper://127.0.0.1:2181?registry-type=service"); + + // 服务提供者协议配置 + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setName("dubbo"); + protocol.setPort(12345); + + ReferenceConfig reference = new ReferenceConfig(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏 + reference.setApplication(application); + reference.setRegistry(registry); // 多个注册中心可以用setRegistries() + reference.setInterface(DemoService.class); + reference.setVersion("1.0.0"); + reference.setProtocol("dubbo"); + reference.setCheck(false); + + // 和本地bean一样使用xxxService + DemoService demoService1 = reference.get(); + + for (int i = 0; i < 1000; i++) { + System.out.println(demoService1.sayName("Hello,World")); + Thread.sleep(1000); + } + + System.in.read(); + } + +} diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboProviderBootstrap.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboProviderBootstrap.java new file mode 100644 index 0000000000..ce5cd6a59e --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/DubboProviderBootstrap.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.config.api.DemoService; +import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter; +import org.apache.dubbo.config.provider.impl.DemoServiceImpl; +import org.apache.dubbo.metadata.MetadataServiceExporter; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceDiscoveryFactory; + +import java.io.IOException; + +/** + * Dubbo Provider Bootstrap + */ +public class DubboProviderBootstrap { + + public static void main(String[] args) throws IOException { + + ApplicationConfig application = new ApplicationConfig(); + application.setName("dubbo-provider-demo"); + + URL connectionURL = URL.valueOf("zookeeper://127.0.0.1:2181?registry-type=service"); + + // 连接注册中心配置 + RegistryConfig registry = new RegistryConfig(); + registry.setAddress(connectionURL.toString()); + + // 服务提供者协议配置 + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setName("dubbo"); + protocol.setPort(NetUtils.getAvailablePort()); + + DemoService demoService = new DemoServiceImpl(); + + ServiceConfig service = new ServiceConfig<>(); + service.setApplication(application); + service.setRegistry(registry); // 多个注册中心可以用setRegistries() + service.setProtocol(protocol); // 多个协议可以用setProtocols() + service.setInterface(DemoService.class); + service.setRef(demoService); + service.setVersion("1.0.0"); + + // 暴露及注册服务 + service.export(); + + MetadataServiceExporter exporter = new ConfigurableMetadataServiceExporter(); + + // 暴露 MetadataService 服务 + exporter.export(); + + ServiceDiscoveryFactory factory = ServiceDiscoveryFactory.getDefaultExtension(); + + ServiceDiscovery serviceDiscovery = factory.create(connectionURL); + + serviceDiscovery.start(); + + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(application.getName(), "127.0.0.1", protocol.getPort()); + + serviceDiscovery.register(serviceInstance); + + System.in.read(); + + serviceDiscovery.stop(); + } +} diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java index d1e02636dd..f9afb35594 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ReferenceConfigTest.java @@ -21,20 +21,30 @@ import org.apache.dubbo.config.annotation.Method; import org.apache.dubbo.config.annotation.Reference; import org.apache.dubbo.config.api.DemoService; import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.event.ReferenceConfigDestroyedEvent; +import org.apache.dubbo.config.event.ReferenceConfigInitializedEvent; import org.apache.dubbo.config.provider.impl.DemoServiceImpl; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.event.EventListener; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.concurrent.atomic.AtomicReference; + import static org.apache.dubbo.rpc.Constants.LOCAL_PROTOCOL; +import static org.junit.jupiter.api.Assertions.assertEquals; public class ReferenceConfigTest { + private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + @BeforeEach public void setUp() { ConfigManager.getInstance().clear(); + eventDispatcher.removeAllEventListeners(); } @AfterEach @@ -67,12 +77,38 @@ public class ReferenceConfigTest { rc.setInterface(DemoService.class.getName()); rc.setInjvm(false); + AtomicReference reference = new AtomicReference<>(); + + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ReferenceConfigInitializedEvent event) { + reference.set(event.getReferenceConfig()); + } + }); + try { System.setProperty("java.net.preferIPv4Stack", "true"); demoService.export(); rc.get(); + + assertEquals(rc, reference.get()); + + reference.compareAndSet(rc, null); + Assertions.assertTrue(!LOCAL_PROTOCOL.equalsIgnoreCase( rc.getInvoker().getUrl().getProtocol())); + + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ReferenceConfigDestroyedEvent event) { + reference.set(event.getReferenceConfig()); + } + }); + + rc.destroy(); + + assertEquals(rc, reference.get()); + } finally { System.clearProperty("java.net.preferIPv4Stack"); demoService.unexport(); @@ -134,17 +170,17 @@ public class ReferenceConfigTest { Reference reference = getClass().getDeclaredField("innerTest").getAnnotation(Reference.class); ReferenceConfig referenceConfig = new ReferenceConfig(reference); Assertions.assertTrue(referenceConfig.getMethods().size() == 1); - Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getName(), "sayHello"); + assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getName(), "sayHello"); Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getTimeout() == 1300); Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getRetries() == 4); - Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getLoadbalance(), "random"); + assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getLoadbalance(), "random"); Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getActives() == 3); Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).getExecutes() == 5); Assertions.assertTrue(((MethodConfig) referenceConfig.getMethods().get(0)).isAsync()); - Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOninvoke(), "i"); - Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnreturn(), "r"); - Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnthrow(), "t"); - Assertions.assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getCache(), "c"); + assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOninvoke(), "i"); + assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnreturn(), "r"); + assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getOnthrow(), "t"); + assertEquals(((MethodConfig) referenceConfig.getMethods().get(0)).getCache(), "c"); } diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/RegistryConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/RegistryConfigTest.java index 51d91fc601..c7b1c153a9 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/RegistryConfigTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/RegistryConfigTest.java @@ -23,8 +23,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY; +import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; 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 ee2fea931d..e3d4b741e3 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 @@ -21,10 +21,14 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.config.api.DemoService; import org.apache.dubbo.config.api.Greeting; import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.event.ServiceConfigExportedEvent; +import org.apache.dubbo.config.event.ServiceConfigUnexportedEvent; import org.apache.dubbo.config.mock.MockProtocol2; import org.apache.dubbo.config.mock.MockRegistryFactory2; import org.apache.dubbo.config.mock.TestProxyFactory; import org.apache.dubbo.config.provider.impl.DemoServiceImpl; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.event.EventListener; import org.apache.dubbo.registry.Registry; import org.apache.dubbo.rpc.Exporter; import org.apache.dubbo.rpc.Invoker; @@ -40,22 +44,23 @@ import org.mockito.Mockito; import java.util.Collections; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY; import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; -import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; -import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; +import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY; import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY; import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY; import static org.apache.dubbo.rpc.Constants.GENERIC_KEY; import static org.apache.dubbo.rpc.Constants.GENERIC_SERIALIZATION_BEAN; import static org.apache.dubbo.rpc.Constants.GENERIC_SERIALIZATION_DEFAULT; import static org.apache.dubbo.rpc.Constants.GENERIC_SERIALIZATION_NATIVE_JAVA; +import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -75,6 +80,8 @@ public class ServiceConfigTest { private ServiceConfig service2 = new ServiceConfig(); private ServiceConfig delayService = new ServiceConfig(); + private final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + @BeforeEach public void setUp() throws Exception { MockProtocol2.delegate = protocolDelegate; @@ -135,8 +142,20 @@ public class ServiceConfigTest { @Test public void testExport() throws Exception { + + AtomicReference reference = new AtomicReference(); + + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceConfigExportedEvent event) { + reference.set(event.getServiceConfig()); + } + }); + service.export(); + assertEquals(service, reference.get()); + assertThat(service.getExportedUrls(), hasSize(1)); URL url = service.toUrl(); assertThat(url.getProtocol(), equalTo("mockprotocol2")); @@ -178,8 +197,32 @@ public class ServiceConfigTest { public void testUnexport() throws Exception { System.setProperty(SHUTDOWN_WAIT_KEY, "0"); try { + AtomicReference reference = new AtomicReference(); + + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceConfigExportedEvent event) { + reference.set(event.getServiceConfig()); + } + }); + service.export(); + + assertEquals(service, reference.get()); + + assertTrue(reference.compareAndSet(service, null)); + + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceConfigUnexportedEvent event) { + reference.set(event.getServiceConfig()); + } + }); + service.unexport(); + + assertEquals(service, reference.get()); + Thread.sleep(1000); Mockito.verify(exporter, Mockito.atLeastOnce()).unexport(); } finally { diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/api/DemoService.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/api/DemoService.java index c5bc722658..f5afc9fefa 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/api/DemoService.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/api/DemoService.java @@ -34,4 +34,7 @@ public interface DemoService { int echo(int i); +// default String name() { +// return getClass().getSimpleName(); +// } } \ No newline at end of file diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListenerTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListenerTest.java new file mode 100644 index 0000000000..b2b981f8b6 --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/ServiceNameMappingListenerTest.java @@ -0,0 +1,63 @@ +/* + * 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.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.api.DemoService; +import org.apache.dubbo.config.provider.impl.DemoServiceImpl; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * {@link ServiceNameMappingListener} Test + * + * @since 2.7.3 + */ +public class ServiceNameMappingListenerTest { + + private ServiceConfig service = new ServiceConfig(); + + @BeforeEach + public void init() { + + ApplicationConfig applicationConfig = new ApplicationConfig("app"); + + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setName("mockprotocol"); + + RegistryConfig registry = new RegistryConfig(); + registry.setProtocol("mockprotocol"); + registry.setAddress("N/A"); + + service.setInterface(DemoService.class); + service.setRef(new DemoServiceImpl()); + service.setApplication(applicationConfig); + service.setProtocol(protocolConfig); + service.setRegistry(registry); + } + + @Test + public void testOnEvent() { + + service.export(); + + } +} diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java new file mode 100644 index 0000000000..66200aca1e --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/metadata/ConfigurableMetadataServiceExporterTest.java @@ -0,0 +1,88 @@ +/* + * 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.config.ProtocolConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.metadata.MetadataServiceExporter; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link ConfigurableMetadataServiceExporter} Test + * + * @since 2.7.3 + */ +public class ConfigurableMetadataServiceExporterTest { + + @BeforeAll + public static void init() { + ConfigManager configManager = ConfigManager.getInstance(); + ApplicationConfig applicationConfig = new ApplicationConfig(); + applicationConfig.setName("test"); + configManager.setApplication(applicationConfig); + + // Add ProtocolConfig + configManager.addProtocol(protocolConfig()); + // Add RegistryConfig + configManager.addRegistry(registryConfig()); + } + + private static ProtocolConfig protocolConfig() { + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setName("mockprotocol"); + protocolConfig.setPort(20880); + return protocolConfig; + } + + private static RegistryConfig registryConfig() { + RegistryConfig registryConfig = new RegistryConfig(); + registryConfig.setAddress("mockregistry://127.0.0.1"); + return registryConfig; + } + + @Test + public void testExportAndUnexport() { + MetadataServiceExporter exporter = new ConfigurableMetadataServiceExporter(); + List urls = exporter.export(); + + assertEquals(1, urls.size()); + + URL url = urls.get(0); + + assertEquals("test", url.getParameter(APPLICATION_KEY)); + assertEquals(MetadataService.class.getName(), url.getServiceInterface()); + assertEquals("test", url.getParameter(GROUP_KEY)); + assertEquals(MetadataService.VERSION, url.getParameter(VERSION_KEY)); + assertEquals("mockprotocol", url.getProtocol()); + + exporter.unexport(); + } + +} diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/provider/impl/DemoServiceImpl.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/provider/impl/DemoServiceImpl.java index d81269e963..1b213281b2 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/provider/impl/DemoServiceImpl.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/provider/impl/DemoServiceImpl.java @@ -16,20 +16,27 @@ */ package org.apache.dubbo.config.provider.impl; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.config.api.Box; import org.apache.dubbo.config.api.DemoException; import org.apache.dubbo.config.api.DemoService; import org.apache.dubbo.config.api.User; +import org.apache.dubbo.rpc.RpcContext; import java.util.List; +import static java.lang.String.format; + /** * DemoServiceImpl */ public class DemoServiceImpl implements DemoService { + private final Logger logger = LoggerFactory.getLogger(getClass()); + public String sayName(String name) { - return "say:" + name; + return log("say:" + name); } public Box getBox() { @@ -48,4 +55,12 @@ public class DemoServiceImpl implements DemoService { return i; } + private T log(T object) { + RpcContext rpcContext = RpcContext.getContext(); + logger.info(format("RPC Invocation [ client - %s:%s , provider - %s:%s] : %s", + rpcContext.getRemoteHost(), rpcContext.getRemotePort(), + rpcContext.getLocalHost(), rpcContext.getLocalPort(), + object)); + return object; + } } \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/SimpleRegistryExporter.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/SimpleRegistryExporter.java index 85cc968ce5..1be3a417fe 100644 --- a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/SimpleRegistryExporter.java +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/SimpleRegistryExporter.java @@ -27,10 +27,10 @@ import org.apache.dubbo.rpc.ProxyFactory; import java.io.IOException; import java.net.ServerSocket; -import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; +import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.rpc.Constants.CALLBACK_INSTANCES_LIMIT_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY; /** * SimpleRegistryExporter diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java index 23fc0f5e8b..804dd8bd56 100644 --- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/DynamicConfiguration.java @@ -19,7 +19,12 @@ package org.apache.dubbo.configcenter; import org.apache.dubbo.common.config.Configuration; import org.apache.dubbo.common.config.Environment; +import java.util.Collections; import java.util.Optional; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; @@ -28,12 +33,13 @@ import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoad *
    * From the use scenario internally in framework, there're mainly three kinds of methods: *

      - *
    • 1. getConfig, get governance rules or single config item from Config Center.
    • - *
    • 2. getConfigFile, get configuration file from Config Center at start up.
    • - *
    • 3. addListener/removeListener, add or remove listeners for governance rules or config items that need to watch.
    • + *
    • 1. getConfig, get governance rules or single config item from Config Center.
    • + *
    • 2. getConfigFile, get configuration file from Config Center at start up.
    • + *
    • 3. addListener/removeListener, add or remove listeners for governance rules or config items that need to watch.
    • *
    */ public interface DynamicConfiguration extends Configuration { + String DEFAULT_GROUP = "dubbo"; /** @@ -113,7 +119,7 @@ public interface DynamicConfiguration extends Configuration { /** * {@see #getConfig(String, String, long)} - * + *

    * This method are mostly used to get a compound config file, such as a complete dubbo.properties file. */ default String getConfigs(String key, String group) throws IllegalStateException { @@ -122,11 +128,67 @@ public interface DynamicConfiguration extends Configuration { /** * {@see #getConfig(String, String, long)} - * + *

    * This method are mostly used to get a compound config file, such as a complete dubbo.properties file. */ String getConfigs(String key, String group, long timeout) throws IllegalStateException; + /** + * Publish Config mapped to the given key and the given group. + * + * @param key the key to represent a configuration + * @param group the group where the key belongs to + * @param content the content of configuration + * @return true if success, or false + * @throws UnsupportedOperationException If the under layer does not support + * @since 2.7.3 + */ + default boolean publishConfig(String key, String group, String content) throws UnsupportedOperationException { + throw new UnsupportedOperationException("No support"); + } + + /** + * Get the config keys by the specified group + * + * @param group the specified group + * @return the read-only non-null sorted {@link Set set} of config keys + * @throws UnsupportedOperationException If the under layer does not support + * @since 2.7.3 + */ + default SortedSet getConfigKeys(String group) throws UnsupportedOperationException { + throw new UnsupportedOperationException("No support"); + } + + /** + * Get the {@link SortedMap} with with config keys and contents value by the specified group + * + * @param group the specified group + * @return the read-only non-null sorted {@link SortedMap map} + * @throws UnsupportedOperationException If the under layer does not support + * @since 2.7.3 + */ + default SortedMap getConfigs(String group) throws UnsupportedOperationException { + return getConfigs(group, -1); + } + + /** + * Get the {@link SortedMap} with with config keys and content value by the specified group + * + * @param group the specified group + * @param timeout the millisecond for timeout + * @return the read-only non-null sorted {@link SortedMap map} + * @throws UnsupportedOperationException If the under layer does not support + * @throws IllegalStateException If timeout exceeds + * @since 2.7.3 + */ + default SortedMap getConfigs(String group, long timeout) throws UnsupportedOperationException, + IllegalStateException { + SortedMap configs = new TreeMap<>(); + SortedSet configKeys = getConfigKeys(group); + configKeys.forEach(key -> configs.put(key, getConfig(key, group, timeout))); + return Collections.unmodifiableSortedMap(configs); + } + /** * Find DynamicConfiguration instance * diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java index dbb91fdffe..56e1191aa5 100644 --- a/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-api/src/main/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfiguration.java @@ -20,6 +20,10 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.configcenter.ConfigurationListener; import org.apache.dubbo.configcenter.DynamicConfiguration; +import java.util.SortedSet; + +import static java.util.Collections.emptySortedSet; + /** * The default extension of {@link DynamicConfiguration}. If user does not specify a config centre, or specifies one * that is not a valid extension, it will default to this one. @@ -30,7 +34,6 @@ public class NopDynamicConfiguration implements DynamicConfiguration { // no-op } - @Override public Object getInternalProperty(String key) { return null; @@ -55,4 +58,20 @@ public class NopDynamicConfiguration implements DynamicConfiguration { public String getConfigs(String key, String group, long timeout) throws IllegalStateException { return null; } + + /** + * @since 2.7.3 + */ + @Override + public boolean publishConfig(String key, String group, String content) { + return true; + } + + /** + * @since 2.7.3 + */ + @Override + public SortedSet getConfigKeys(String group) { + return emptySortedSet(); + } } diff --git a/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java new file mode 100644 index 0000000000..cd549dd62c --- /dev/null +++ b/dubbo-configcenter/dubbo-configcenter-api/src/test/java/org/apache/dubbo/configcenter/support/nop/NopDynamicConfigurationTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.configcenter.support.nop; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link NopDynamicConfiguration} Test + * + * @since 2.7.3 + */ +public class NopDynamicConfigurationTest { + + private NopDynamicConfiguration configuration = new NopDynamicConfiguration(null); + + @Test + public void testGetInternalProperty() { + assertNull(configuration.getInternalProperty(null)); + } + + @Test + public void testGetConfig() { + assertNull(configuration.getConfig(null, null, -1)); + } + + + @Test + public void testGetConfigs() { + assertNull(configuration.getConfigs(null, null, -1)); + } + + @Test + public void testAddListener() { + configuration.addListener(null, null, null); + } + + @Test + public void testRemoveListener() { + configuration.removeListener(null, null, null); + } + + @Test + public void testPublishConfig() { + assertTrue(configuration.publishConfig(null, null, null)); + } + + @Test + public void testGetConfigKeys() { + assertTrue(configuration.getConfigKeys(null).isEmpty()); + } + + +} diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java index a06537b432..5b034ca374 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java @@ -27,16 +27,25 @@ import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import static java.util.Collections.emptySortedSet; +import static java.util.Collections.unmodifiableSortedSet; +import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty; import static org.apache.dubbo.configcenter.Constants.CONFIG_NAMESPACE_KEY; /** * */ public class ZookeeperDynamicConfiguration implements DynamicConfiguration { + + private static final String EMPTY_STRING = ""; + private static final Logger logger = LoggerFactory.getLogger(ZookeeperDynamicConfiguration.class); private Executor executor; @@ -91,24 +100,8 @@ public class ZookeeperDynamicConfiguration implements DynamicConfiguration { @Override public String getConfig(String key, String group, long timeout) throws IllegalStateException { - /** - * when group is not null, we are getting startup configs from Config Center, for example: - * group=dubbo, key=dubbo.properties - */ - if (StringUtils.isNotEmpty(group)) { - key = group + "/" + key; - } - /** - * when group is null, we are fetching governance rules, for example: - * 1. key=org.apache.dubbo.DemoService.configurators - * 2. key = org.apache.dubbo.DemoService.condition-router - */ - else { - int i = key.lastIndexOf("."); - key = key.substring(0, i) + "/" + key.substring(i + 1); - } - - return (String) getInternalProperty(rootPath + "/" + key); + String path = buildPath(key, group); + return (String) getInternalProperty(path); } /** @@ -122,6 +115,54 @@ public class ZookeeperDynamicConfiguration implements DynamicConfiguration { */ @Override public String getConfigs(String key, String group, long timeout) throws IllegalStateException { - return (String) getConfig(key, group, timeout); + return getConfig(key, group, timeout); } + + @Override + public boolean publishConfig(String key, String group, String content) { + String path = buildPath(key, group); + zkClient.create(path, content, true); + return true; + } + + @Override + public SortedSet getConfigKeys(String group) { + String path = buildPath(group); + List nodes = zkClient.getChildren(path); + return isEmpty(nodes) ? emptySortedSet() : unmodifiableSortedSet(new TreeSet<>(nodes)); + } + + /** + * Build the config node path by the specified key and group + * + * @param key the key to represent a configuration + * @param group the group where the key belongs to + * @return + */ + protected String buildPath(String key, String group) { + String path = null; + /** + * when group is not null, we are getting startup configs from Config Center, for example: + * group=dubbo, key=dubbo.properties + */ + if (StringUtils.isNotEmpty(group)) { + path = group + "/" + key; + } + /** + * when group is null, we are fetching governance rules, for example: + * 1. key=org.apache.dubbo.DemoService.configurators + * 2. key = org.apache.dubbo.DemoService.condition-router + */ + else { + int i = key.lastIndexOf("."); + path = key.substring(0, i) + "/" + key.substring(i + 1); + } + return buildPath(path); + } + + protected String buildPath(String relativePath) { + String path = rootPath + "/" + relativePath; + return path; + } + } diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java index 64e0becc64..df440acc62 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java @@ -35,8 +35,14 @@ import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.CountDownLatch; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * TODO refactor using mockito */ @@ -122,6 +128,39 @@ public class ZookeeperDynamicConfigurationTest { Assertions.assertEquals("new value2", listener4.getValue()); } + @Test + public void testPublishConfig() { + String key = "user-service"; + String group = "org.apache.dubbo.service.UserService"; + String content = "test"; + + assertTrue(configuration.publishConfig(key, group, content)); + assertEquals("test", configuration.getConfigs(key, group)); + } + + @Test + public void testGetConfigKeysAndContents() { + + String key = "user-service"; + String group = "org.apache.dubbo.service.UserService"; + String content = "test"; + + String key2 = "user-service-1"; + + assertTrue(configuration.publishConfig(key, group, content)); + assertTrue(configuration.publishConfig(key2, group, content)); + + Set configKeys = configuration.getConfigKeys(group); + + assertEquals(new TreeSet(asList(key, key2)), configKeys); + + Map configs = configuration.getConfigs(group); + + assertEquals(configs.keySet(), configKeys); + + configs.forEach((k, value) -> assertEquals(content, value)); + } + private class TestListener implements ConfigurationListener { private CountDownLatch latch; private String value; diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 3573df1ed3..3517999c38 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -153,7 +153,7 @@ 6.1.26 2.0 1.1.0 - 2.7.2-SNAPSHOT + 2.7.3-SNAPSHOT @@ -228,6 +228,17 @@ + + org.apache.curator + curator-x-discovery + ${curator_version} + + + org.apache.zookeeper + zookeeper + + + redis.clients jedis diff --git a/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml b/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml index 66dba910f4..968e5e35bc 100644 --- a/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml +++ b/dubbo-dependencies/dubbo-dependencies-zookeeper/pom.xml @@ -32,7 +32,7 @@ pom - 2.7.2-SNAPSHOT + 2.7.3-SNAPSHOT 1.1.0 diff --git a/dubbo-event/pom.xml b/dubbo-event/pom.xml new file mode 100644 index 0000000000..017f0e79fd --- /dev/null +++ b/dubbo-event/pom.xml @@ -0,0 +1,45 @@ + + + + org.apache.dubbo + dubbo-parent + ${revision} + ../pom.xml + + 4.0.0 + + dubbo-event + jar + + dubbo-event + The event module of Dubbo project + + + + org.apache.dubbo + dubbo-common + ${project.parent.version} + true + + + + + + \ No newline at end of file diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/AbstractEventDispatcher.java b/dubbo-event/src/main/java/org/apache/dubbo/event/AbstractEventDispatcher.java new file mode 100644 index 0000000000..4b714f7963 --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/AbstractEventDispatcher.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.event; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +import static java.util.Collections.sort; +import static java.util.Collections.unmodifiableList; +import static java.util.ServiceLoader.load; +import static org.apache.dubbo.event.EventListener.findEventType; + +/** + * The abstract {@link EventDispatcher} providers the common implementation. + * + * @see EventDispatcher + * @see Listenable + * @see ServiceLoader + * @see EventListener + * @see Event + * @since 2.7.3 + */ +public abstract class AbstractEventDispatcher implements EventDispatcher { + + private final Object mutex = new Object(); + + private final ConcurrentMap, List> listenersCache = new ConcurrentHashMap<>(); + + private final Executor executor; + + /** + * Constructor with an instance of {@link Executor} + * + * @param executor {@link Executor} + * @throws NullPointerException executor is null + */ + protected AbstractEventDispatcher(Executor executor) { + if (executor == null) { + throw new NullPointerException("executor must not be null"); + } + this.executor = executor; + this.loadEventListenerInstances(); + } + + @Override + public void addEventListener(EventListener listener) throws NullPointerException, IllegalArgumentException { + Listenable.assertListener(listener); + doInListener(listener, listeners -> { + addIfAbsent(listeners, listener); + }); + } + + @Override + public void removeEventListener(EventListener listener) throws NullPointerException, IllegalArgumentException { + Listenable.assertListener(listener); + doInListener(listener, listeners -> listeners.remove(listener)); + } + + @Override + public List> getAllEventListeners() { + List> listeners = new LinkedList<>(); + + listenersCache + .entrySet() + .stream() + .map(Map.Entry::getValue) + .flatMap(Collection::stream) + .forEach(listener -> { + addIfAbsent(listeners, listener); + }); + + sort((List) listeners); + + return unmodifiableList(listeners); + } + + private void addIfAbsent(Collection collection, E element) { + if (!collection.contains(element)) { + collection.add(element); + } + } + + @Override + public void dispatch(Event event) { + + Executor executor = getExecutor(); + + // execute in sequential or parallel execution model + executor.execute(() -> { + listenersCache.entrySet() + .stream() + .filter(entry -> entry.getKey().isAssignableFrom(event.getClass())) + .map(Map.Entry::getValue) + .flatMap(Collection::stream) + .forEach(listener -> { + listener.onEvent(event); + }); + }); + } + + /** + * @return the non-null {@link Executor} + */ + @Override + public final Executor getExecutor() { + return executor; + } + + protected void doInListener(EventListener listener, Consumer> consumer) { + Class eventType = findEventType(listener); + if (eventType != null) { + synchronized (mutex) { + List listeners = listenersCache.computeIfAbsent(eventType, e -> new LinkedList<>()); + // consume + consumer.accept(listeners); + // sort + sort(listeners); + } + } + } + + /** + * Default, load the instances of {@link EventListener event listeners} by {@link ServiceLoader} + *

    + * It could be override by the sub-class + * + * @see EventListener + * @see ServiceLoader#load(Class) + */ + protected void loadEventListenerInstances() { + ServiceLoader serviceLoader = load(EventListener.class, getClass().getClassLoader()); + serviceLoader.forEach(this::addEventListener); + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/DirectEventDispatcher.java b/dubbo-event/src/main/java/org/apache/dubbo/event/DirectEventDispatcher.java new file mode 100644 index 0000000000..f19390ce78 --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/DirectEventDispatcher.java @@ -0,0 +1,30 @@ +/* + * 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.event; + +/** + * Direct {@link EventDispatcher} implementation uses current thread execution model + * + * @see EventDispatcher + * @since 2.7.3 + */ +public final class DirectEventDispatcher extends AbstractEventDispatcher { + + public DirectEventDispatcher() { + super(DIRECT_EXECUTOR); + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/Event.java b/dubbo-event/src/main/java/org/apache/dubbo/event/Event.java new file mode 100644 index 0000000000..c91f287b9b --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/Event.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.event; + +import java.util.EventObject; + +/** + * An event object of Dubbo is based on the Java standard {@link EventObject event} + * + * @since 2.7.3 + */ +public abstract class Event extends EventObject { + + private static final long serialVersionUID = -1704315605423947137L; + + /** + * The timestamp of event occurs + */ + private final long timestamp; + + /** + * Constructs a prototypical Event. + * + * @param source The object on which the Event initially occurred. + * @throws IllegalArgumentException if source is null. + */ + public Event(Object source) { + super(source); + this.timestamp = System.currentTimeMillis(); + } + + public long getTimestamp() { + return timestamp; + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/EventDispatcher.java b/dubbo-event/src/main/java/org/apache/dubbo/event/EventDispatcher.java new file mode 100644 index 0000000000..ece1f5a09b --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/EventDispatcher.java @@ -0,0 +1,66 @@ +/* + * 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.event; + +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.extension.SPI; + +import java.util.concurrent.Executor; + +/** + * {@link Event Dubbo Event} Dispatcher + * + * @see Event + * @see EventListener + * @see DirectEventDispatcher + * @since 2.7.3 + */ +@SPI("direct") +public interface EventDispatcher extends Listenable> { + + /** + * Direct {@link Executor} uses sequential execution model + */ + Executor DIRECT_EXECUTOR = Runnable::run; + + /** + * Dispatch a Dubbo event to the registered {@link EventListener Dubbo event listeners} + * + * @param event a {@link Event Dubbo event} + */ + void dispatch(Event event); + + /** + * The {@link Executor} to dispatch a {@link Event Dubbo event} + * + * @return default implementation directly invoke {@link Runnable#run()} method, rather than multiple-threaded + * {@link Executor}. If the return value is null, the behavior is same as default. + * @see #DIRECT_EXECUTOR + */ + default Executor getExecutor() { + return DIRECT_EXECUTOR; + } + + /** + * The default extension of {@link EventDispatcher} is loaded by {@link ExtensionLoader} + * + * @return the default extension of {@link EventDispatcher} + */ + static EventDispatcher getDefaultExtension() { + return ExtensionLoader.getExtensionLoader(EventDispatcher.class).getDefaultExtension(); + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/EventListener.java b/dubbo-event/src/main/java/org/apache/dubbo/event/EventListener.java new file mode 100644 index 0000000000..d3c0ae371a --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/EventListener.java @@ -0,0 +1,124 @@ +/* + * 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.event; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Objects; + +import static java.lang.Integer.compare; +import static org.apache.dubbo.common.utils.ReflectUtils.findParameterizedTypes; + +/** + * The {@link Event Dubbo Event} Listener that is based on Java standard {@link java.util.EventListener} interface supports + * the generic {@link Event}. + *

    + * The {@link #onEvent(Event) handle method} will be notified when the matched-type {@link Event Dubbo Event} is + * published, whose priority could be changed by {@link #getPriority()} method. + * + * @param the concrete class of {@link Event Dubbo Event} + * @see Event + * @see java.util.EventListener + * @since 2.7.3 + */ +@FunctionalInterface +public interface EventListener extends java.util.EventListener, Comparable> { + + /** + * Handle a {@link Event Dubbo Event} when it's be published + * + * @param event a {@link Event Dubbo Event} + */ + void onEvent(E event); + + /** + * The priority of {@link EventListener current listener}. + * + * @return the value is more greater, the priority is more lower. + * {@link Integer#MIN_VALUE} indicates the highest priority. The default value is {@link Integer#MAX_VALUE}. + * The comparison rule , refer to {@link #compareTo(EventListener)}. + * @see #compareTo(EventListener) + * @see Integer#MAX_VALUE + * @see Integer#MIN_VALUE + */ + default int getPriority() { + return Integer.MAX_VALUE; + } + + @Override + default int compareTo(EventListener another) { + return compare(this.getPriority(), another.getPriority()); + } + + /** + * Find the {@link Class type} {@link Event Dubbo event} from the specified {@link EventListener Dubbo event listener} + * + * @param listener the {@link Class class} of {@link EventListener Dubbo event listener} + * @return null if not found + */ + static Class findEventType(EventListener listener) { + return findEventType(listener.getClass()); + } + + /** + * Find the {@link Class type} {@link Event Dubbo event} from the specified {@link EventListener Dubbo event listener} + * + * @param listenerClass the {@link Class class} of {@link EventListener Dubbo event listener} + * @return null if not found + */ + static Class findEventType(Class listenerClass) { + Class eventType = null; + + if (listenerClass != null && EventListener.class.isAssignableFrom(listenerClass)) { + eventType = findParameterizedTypes(listenerClass) + .stream() + .map(EventListener::findEventType) + .filter(Objects::nonNull) + .findAny() + .orElse((Class) findEventType(listenerClass.getSuperclass())); + } + + return eventType; + } + + /** + * Find the type {@link Event Dubbo event} from the specified {@link ParameterizedType} presents + * a class of {@link EventListener Dubbo event listener} + * + * @param parameterizedType the {@link ParameterizedType} presents a class of {@link EventListener Dubbo event listener} + * @return null if not found + */ + static Class findEventType(ParameterizedType parameterizedType) { + Class eventType = null; + + Type rawType = parameterizedType.getRawType(); + if ((rawType instanceof Class) && EventListener.class.isAssignableFrom((Class) rawType)) { + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + for (Type typeArgument : typeArguments) { + if (typeArgument instanceof Class) { + Class argumentClass = (Class) typeArgument; + if (Event.class.isAssignableFrom(argumentClass)) { + eventType = argumentClass; + break; + } + } + } + } + + return eventType; + } +} \ No newline at end of file diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/GenericEvent.java b/dubbo-event/src/main/java/org/apache/dubbo/event/GenericEvent.java new file mode 100644 index 0000000000..a7e68506a2 --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/GenericEvent.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.event; + +/** + * Generic {@link Event Dubbo event} + * + * @param the type of event source + * @since 2.7.3 + */ +public class GenericEvent extends Event { + + public GenericEvent(S source) { + super(source); + } + + public S getSource() { + return (S) super.getSource(); + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/GenericEventListener.java b/dubbo-event/src/main/java/org/apache/dubbo/event/GenericEventListener.java new file mode 100644 index 0000000000..065be3c553 --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/GenericEventListener.java @@ -0,0 +1,130 @@ +/* + * 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.event; + +import org.apache.dubbo.common.function.ThrowableConsumer; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptySet; +import static java.util.stream.Stream.of; +import static org.apache.dubbo.common.function.ThrowableFunction.execute; + +/** + * An abstract class of {@link EventListener} for Generic events, the sub class could add more {@link Event event} + * handle methods, rather than only binds the {@link EventListener#onEvent(Event)} method that is declared to be + * final the implementation can't override. It's notable that all {@link Event event} handle methods must + * meet following conditions: + *

      + *
    • not {@link #onEvent(Event)} method
    • + *
    • public accessibility
    • + *
    • void return type
    • + *
    • no {@link Exception exception} declaration
    • + *
    • only one {@link Event} type argument
    • + *
    + * + * @see Event + * @see EventListener + * @since 2.7.3 + */ +public abstract class GenericEventListener implements EventListener { + + private final Method onEventMethod; + + private final Map, Set> handleEventMethods; + + protected GenericEventListener() { + this.onEventMethod = findOnEventMethod(); + this.handleEventMethods = findHandleEventMethods(); + } + + private Method findOnEventMethod() { + return execute(getClass(), listenerClass -> listenerClass.getMethod("onEvent", Event.class)); + } + + private Map, Set> findHandleEventMethods() { + // Event class for key, the eventMethods' Set as value + Map, Set> eventMethods = new HashMap<>(); + of(getClass().getMethods()) + .filter(this::isHandleEventMethod) + .forEach(method -> { + Class paramType = method.getParameterTypes()[0]; + Set methods = eventMethods.computeIfAbsent(paramType, key -> new LinkedHashSet<>()); + methods.add(method); + }); + return eventMethods; + } + + public final void onEvent(Event event) { + Class eventClass = event.getClass(); + handleEventMethods.getOrDefault(eventClass, emptySet()).forEach(method -> { + ThrowableConsumer.execute(method, m -> { + m.invoke(this, event); + }); + }); + } + + /** + * The {@link Event event} handle methods must meet following conditions: + *
      + *
    • not {@link #onEvent(Event)} method
    • + *
    • public accessibility
    • + *
    • void return type
    • + *
    • no {@link Exception exception} declaration
    • + *
    • only one {@link Event} type argument
    • + *
    + * + * @param method + * @return + */ + private boolean isHandleEventMethod(Method method) { + + if (onEventMethod.equals(method)) { // not {@link #onEvent(Event)} method + return false; + } + + if (!Modifier.isPublic(method.getModifiers())) { // not public + return false; + } + + if (!void.class.equals(method.getReturnType())) { // void return type + return false; + } + + Class[] exceptionTypes = method.getExceptionTypes(); + + if (exceptionTypes.length > 0) { // no exception declaration + return false; + } + + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { // not only one argument + return false; + } + + if (!Event.class.isAssignableFrom(paramTypes[0])) { // not Event type argument + return false; + } + + return true; + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/Listenable.java b/dubbo-event/src/main/java/org/apache/dubbo/event/Listenable.java new file mode 100644 index 0000000000..845cd99fcf --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/Listenable.java @@ -0,0 +1,131 @@ +/* + * 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.event; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.util.stream.StreamSupport.stream; + +/** + * Dubbo Event Listenable + * + * @see EventDispatcher + * @since 2.7.3 + */ +public interface Listenable> { + + /** + * Add a {@link EventListener Dubbo event listener} + * + * @param listener a {@link EventListener Dubbo event listener} + * If current {@link EventListener} is existed, return false + * @throws NullPointerException if listener argument is null + * @throws IllegalArgumentException if listener argument is not concrete instance + */ + void addEventListener(E listener) throws NullPointerException, IllegalArgumentException; + + /** + * Add one or more {@link EventListener Dubbo event listeners} + * + * @param listener a {@link EventListener Dubbo event listener} + * @param others an optional {@link EventListener Dubbo event listeners} + * @throws NullPointerException if one of arguments is null + * @throws IllegalArgumentException if one of arguments argument is not concrete instance + */ + default void addEventListeners(E listener, E... others) throws NullPointerException, + IllegalArgumentException { + List listeners = new ArrayList<>(1 + others.length); + listeners.add(listener); + listeners.addAll(Arrays.asList(others)); + addEventListeners(listeners); + } + + /** + * Add multiple {@link EventListener Dubbo event listeners} + * + * @param listeners the {@link EventListener Dubbo event listeners} + * @throws NullPointerException if listeners argument is null + * @throws IllegalArgumentException if any element of listeners is not concrete instance + */ + default void addEventListeners(Iterable listeners) throws NullPointerException, IllegalArgumentException { + stream(listeners.spliterator(), false).forEach(this::addEventListener); + } + + /** + * Remove a {@link EventListener Dubbo event listener} + * + * @param listener a {@link EventListener Dubbo event listener} + * @return If remove successfully, return true. + * If current {@link EventListener} is existed, return false + * @throws NullPointerException if listener argument is null + */ + void removeEventListener(E listener) throws NullPointerException, IllegalArgumentException; + + /** + * Remove a {@link EventListener Dubbo event listener} + * + * @param listeners the {@link EventListener Dubbo event listeners} + * @return If remove successfully, return true. + * If current {@link EventListener} is existed, return false + * @throws NullPointerException if listener argument is null + * @throws IllegalArgumentException if any element of listeners is not concrete instance + */ + default void removeEventListeners(Iterable listeners) throws NullPointerException, IllegalArgumentException { + stream(listeners.spliterator(), false).forEach(this::removeEventListener); + } + + /** + * Remove all {@link EventListener Dubbo event listeners} + * + * @return a amount of removed listeners + */ + default void removeAllEventListeners() { + removeEventListeners(getAllEventListeners()); + } + + /** + * Get all registered {@link EventListener Dubbo event listeners} + * + * @return non-null read-only ordered {@link EventListener Dubbo event listeners} + * @see EventListener#getPriority() + */ + List getAllEventListeners(); + + + /** + * Assets the listener is valid or not + * + * @param listener the instance of {@link EventListener} + * @throws NullPointerException + */ + static void assertListener(EventListener listener) throws NullPointerException { + if (listener == null) { + throw new NullPointerException("The listener must not be null."); + } + + Class listenerClass = listener.getClass(); + + int modifiers = listenerClass.getModifiers(); + + if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers)) { + throw new IllegalArgumentException("The listener must be concrete class"); + } + } +} diff --git a/dubbo-event/src/main/java/org/apache/dubbo/event/ParallelEventDispatcher.java b/dubbo-event/src/main/java/org/apache/dubbo/event/ParallelEventDispatcher.java new file mode 100644 index 0000000000..73288ea4f1 --- /dev/null +++ b/dubbo-event/src/main/java/org/apache/dubbo/event/ParallelEventDispatcher.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.event; + +import java.util.concurrent.ForkJoinPool; + +/** + * Parallel {@link EventDispatcher} implementation uses {@link ForkJoinPool#commonPool() JDK common thread pool} + * + * @see ForkJoinPool#commonPool() + * @since 2.7.3 + */ +public class ParallelEventDispatcher extends AbstractEventDispatcher { + + public ParallelEventDispatcher() { + super(ForkJoinPool.commonPool()); + } +} diff --git a/dubbo-event/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventDispatcher b/dubbo-event/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventDispatcher new file mode 100644 index 0000000000..ecd54edb28 --- /dev/null +++ b/dubbo-event/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.event.EventDispatcher @@ -0,0 +1,2 @@ +direct=org.apache.dubbo.event.DirectEventDispatcher +parallel=org.apache.dubbo.event.ParallelEventDispatcher diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/AbstractEventListener.java b/dubbo-event/src/test/java/org/apache/dubbo/event/AbstractEventListener.java new file mode 100644 index 0000000000..692b3b6d12 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/AbstractEventListener.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.event; + +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class AbstractEventListener implements EventListener { + + private final AtomicInteger eventOccurs = new AtomicInteger(0); + + @Override + public final void onEvent(E event) { + eventOccurs.getAndIncrement(); + handleEvent(event); + } + + protected abstract void handleEvent(E event); + + public int getEventOccurs() { + return eventOccurs.get(); + } + + protected void println(String message) { + System.out.printf("[%s] %s\n", Thread.currentThread().getName(), message); + } +} \ No newline at end of file diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/DirectEventDispatcherTest.java b/dubbo-event/src/test/java/org/apache/dubbo/event/DirectEventDispatcherTest.java new file mode 100644 index 0000000000..ae7a9e8a22 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/DirectEventDispatcherTest.java @@ -0,0 +1,153 @@ +/* + * 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.event; + +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 java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link DirectEventDispatcher} Test + * + * @since 2.7.3 + */ +public class DirectEventDispatcherTest { + + private DirectEventDispatcher dispatcher; + + private EchoEventListener echoEventListener; + + private EchoEventListener2 echoEventListener2; + + @BeforeEach + public void init() { + dispatcher = new DirectEventDispatcher(); + echoEventListener = new EchoEventListener(); + echoEventListener2 = new EchoEventListener2(); + } + + @AfterEach + public void destroy() { + dispatcher.removeAllEventListeners(); + } + + @Test + public void testGetExecutor() { + assertNotNull(dispatcher.getExecutor()); + } + + @Test + public void testGetAllListeners() { + assertTrue(dispatcher.getAllEventListeners().isEmpty()); + } + + @Test + public void testSingleListener() { + // add two listeners + dispatcher.addEventListener(echoEventListener); + dispatcher.addEventListener(echoEventListener2); + assertEquals(asList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); + + // add a duplicated listener + dispatcher.addEventListener(echoEventListener); + assertEquals(asList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); + + // remove + dispatcher.removeEventListener(echoEventListener); + assertEquals(asList(echoEventListener2), dispatcher.getAllEventListeners()); + + dispatcher.removeEventListener(echoEventListener2); + assertEquals(emptyList(), dispatcher.getAllEventListeners()); + } + + @Test + public void testMultipleListeners() { + + // add two listeners + dispatcher.addEventListeners(echoEventListener, echoEventListener2); + assertEquals(asList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); + + // remove all listeners + dispatcher.removeAllEventListeners(); + assertEquals(emptyList(), dispatcher.getAllEventListeners()); + + // add the duplicated listeners + dispatcher.addEventListeners(echoEventListener, echoEventListener, echoEventListener2); + assertEquals(asList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); + + // remove all listeners + dispatcher.removeAllEventListeners(); + assertEquals(emptyList(), dispatcher.getAllEventListeners()); + + dispatcher.addEventListeners(asList(echoEventListener, echoEventListener, echoEventListener2)); + assertEquals(asList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); + + dispatcher.removeEventListeners(asList(echoEventListener, echoEventListener, echoEventListener2)); + assertEquals(emptyList(), dispatcher.getAllEventListeners()); + } + + @Test + public void testDispatchEvent() { + + dispatcher.addEventListener(echoEventListener); + + // dispatch a Event + dispatcher.dispatch(new Event("Test") { + }); + + // no-op occurs + assertEquals(0, echoEventListener.getEventOccurs()); + + // dispatch a EchoEvent + dispatcher.dispatch(new EchoEvent("Hello,World")); + + // event has been handled + assertEquals(1, echoEventListener.getEventOccurs()); + + dispatcher.addEventListener(echoEventListener2); + + // reset the listeners + init(); + dispatcher.addEventListeners(echoEventListener, echoEventListener2); + + // dispatch a Event + dispatcher.dispatch(new Event("Test") { + }); + + // echoEventListener will be not triggered + 0 + // echoEventListener2 will be triggered + 1 + assertEquals(0, echoEventListener.getEventOccurs()); + assertEquals(1, echoEventListener2.getEventOccurs()); + + // dispatch a EchoEvent + // echoEventListener and echoEventListener2 are triggered both (+1) + dispatcher.dispatch(new EchoEvent("Hello,World")); + assertEquals(1, echoEventListener.getEventOccurs()); + assertEquals(2, echoEventListener2.getEventOccurs()); + + // both +1 + dispatcher.dispatch(new EchoEvent("2019")); + assertEquals(2, echoEventListener.getEventOccurs()); + assertEquals(3, echoEventListener2.getEventOccurs()); + } +} diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEvent.java b/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEvent.java new file mode 100644 index 0000000000..01f1ab07f5 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEvent.java @@ -0,0 +1,29 @@ +/* + * 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.event; + +/** + * Echo {@link Event} + * + * @since 2.7.3 + */ +class EchoEvent extends Event { + + public EchoEvent(Object source) { + super(source); + } +} diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener.java b/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener.java new file mode 100644 index 0000000000..5d6beb236c --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.event; + +import java.io.Serializable; + +/** + * {@link EchoEvent} {@link EventListener} + * + * @since 2.7.3 + */ +public class EchoEventListener extends AbstractEventListener implements Serializable { + + @Override + public void handleEvent(EchoEvent event) { + println("EchoEventListener : " + event); + } +} \ No newline at end of file diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener2.java b/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener2.java new file mode 100644 index 0000000000..df991e5913 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/EchoEventListener2.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.event; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Vector; + +/** + * {@link EchoEvent} {@link EventListener} 2 + * + * @since 2.7.3 + */ +public class EchoEventListener2 extends Vector> implements Serializable, Comparable>, + EventListener { + + private AbstractEventListener delegate = new AbstractEventListener() { + @Override + protected void handleEvent(Event event) { + println("EchoEventListener2 : " + event); + } + }; + + @Override + public void onEvent(Event event) { + delegate.onEvent(event); + } + + @Override + public int getPriority() { + return 0; + } + + public int getEventOccurs() { + return delegate.getEventOccurs(); + } + + @Override + public boolean equals(Object o) { + return this.getClass().equals(o.getClass()); + } + + @Override + public int hashCode() { + return Objects.hash(this.getClass()); + } +} diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/EventDispatcherTest.java b/dubbo-event/src/test/java/org/apache/dubbo/event/EventDispatcherTest.java new file mode 100644 index 0000000000..001bfc2937 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/EventDispatcherTest.java @@ -0,0 +1,45 @@ +/* + * 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.event; + +import org.junit.jupiter.api.Test; + +import static org.apache.dubbo.event.EventDispatcher.DIRECT_EXECUTOR; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link EventDispatcher} Test + * + * @see DirectEventDispatcher + * @since 2.7.3 + */ +public class EventDispatcherTest { + + private EventDispatcher defaultInstance = EventDispatcher.getDefaultExtension(); + + @Test + public void testDefaultInstance() { + assertEquals(DirectEventDispatcher.class, defaultInstance.getClass()); + } + + @Test + public void testDefaultMethods() { + assertEquals(DIRECT_EXECUTOR, defaultInstance.getExecutor()); + assertTrue(defaultInstance.getAllEventListeners().isEmpty()); + } +} diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/EventListenerTest.java b/dubbo-event/src/test/java/org/apache/dubbo/event/EventListenerTest.java new file mode 100644 index 0000000000..fc831dda02 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/EventListenerTest.java @@ -0,0 +1,44 @@ +/* + * 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.event; + +import org.junit.jupiter.api.Test; + +import static org.apache.dubbo.event.EventListener.findEventType; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link EventListener} Test + * + * @since 2.7.3 + */ +public class EventListenerTest { + + @Test + public void testFindEventHierarchicalTypes() { + assertEquals(EchoEvent.class, findEventType(new EchoEventListener())); + assertEquals(Event.class, findEventType(new EchoEventListener2())); + + assertEquals(EchoEvent.class, findEventType(EchoEventListener.class)); + assertEquals(Event.class, findEventType(EchoEventListener2.class)); + } + + @Test + public void testOnEvent() { + } + +} \ No newline at end of file diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventListenerTest.java b/dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventListenerTest.java new file mode 100644 index 0000000000..c0ff9a1d41 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventListenerTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.event; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link GenericEventListener} Test + * + * @since 2.7.3 + */ +public class GenericEventListenerTest { + + private EventDispatcher eventDispatcher; + + private MyGenericEventListener listener; + + @BeforeEach + public void init() { + this.listener = new MyGenericEventListener(); + this.eventDispatcher = EventDispatcher.getDefaultExtension(); + this.eventDispatcher.addEventListener(listener); + } + + @AfterEach + public void destroy() { + this.eventDispatcher.removeAllEventListeners(); + } + + @Test + public void testOnEvent() { + String value = "Hello,World"; + EchoEvent echoEvent = new EchoEvent(value); + eventDispatcher.dispatch(echoEvent); + assertEquals(echoEvent, listener.getEchoEvent()); + assertEquals(value, listener.getEchoEvent().getSource()); + } + + class MyGenericEventListener extends GenericEventListener { + + private EchoEvent echoEvent; + + public void onEvent(EchoEvent echoEvent) { + this.echoEvent = echoEvent; + } + + public void event(EchoEvent echoEvent) { + assertEquals("Hello,World", echoEvent.getSource()); + } + + public void event(EchoEvent echoEvent, Object arg) { + this.echoEvent = echoEvent; + } + + public EchoEvent getEchoEvent() { + return echoEvent; + } + } +} diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventTest.java b/dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventTest.java new file mode 100644 index 0000000000..384893fbee --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/GenericEventTest.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.event; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link GenericEvent} Test + * + * @since 2.7.3 + */ +public class GenericEventTest { + + @Test + public void test() { + + long timestamp = System.currentTimeMillis(); + GenericEvent event = new GenericEvent("Hello,World"); + + assertEquals("Hello,World", event.getSource()); + assertTrue(event.getTimestamp() >= timestamp); + } + +} diff --git a/dubbo-event/src/test/java/org/apache/dubbo/event/ParallelEventDispatcherTest.java b/dubbo-event/src/test/java/org/apache/dubbo/event/ParallelEventDispatcherTest.java new file mode 100644 index 0000000000..ae59729fd4 --- /dev/null +++ b/dubbo-event/src/test/java/org/apache/dubbo/event/ParallelEventDispatcherTest.java @@ -0,0 +1,59 @@ +/* + * 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.event; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link ParallelEventDispatcher} Test + * + * @since 2.7.3 + */ +public class ParallelEventDispatcherTest { + + private EventDispatcher eventDispatcher; + + private AbstractEventListener listener; + + @BeforeEach + public void init() { + eventDispatcher = new ParallelEventDispatcher(); + listener = new EchoEventListener(); + eventDispatcher.addEventListener(listener); + } + + @Test + public void testDispatchEvent() throws InterruptedException { + eventDispatcher.dispatch(new EchoEvent("Hello,World")); + ForkJoinPool.commonPool().awaitTermination(1, TimeUnit.SECONDS); + // event has been handled + assertEquals(1, listener.getEventOccurs()); + } + + @AfterAll + public static void destroy() { + ForkJoinPool.commonPool().shutdown(); + } + +} diff --git a/dubbo-event/src/test/resources/META-INF/services/org.apache.dubbo.event.EventListener b/dubbo-event/src/test/resources/META-INF/services/org.apache.dubbo.event.EventListener new file mode 100644 index 0000000000..3cb4b7fdd3 --- /dev/null +++ b/dubbo-event/src/test/resources/META-INF/services/org.apache.dubbo.event.EventListener @@ -0,0 +1 @@ +#org.apache.dubbo.event.EchoEventListener2 \ No newline at end of file diff --git a/dubbo-metadata-report/dubbo-metadata-report-api/src/test/java/org/apache/dubbo/metadata/identifier/MetadataIdentifierTest.java b/dubbo-metadata-report/dubbo-metadata-report-api/src/test/java/org/apache/dubbo/metadata/identifier/MetadataIdentifierTest.java index 2467571ec2..c660a34cd2 100644 --- a/dubbo-metadata-report/dubbo-metadata-report-api/src/test/java/org/apache/dubbo/metadata/identifier/MetadataIdentifierTest.java +++ b/dubbo-metadata-report/dubbo-metadata-report-api/src/test/java/org/apache/dubbo/metadata/identifier/MetadataIdentifierTest.java @@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test; import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE; -import static org.apache.dubbo.metadata.identifier.MetadataIdentifier.META_DATA_STORE_TAG; /** * 2019/1/7 diff --git a/dubbo-metadata-report/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java b/dubbo-metadata-report/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java index 85e95acbc0..6f3810476c 100644 --- a/dubbo-metadata-report/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java +++ b/dubbo-metadata-report/dubbo-metadata-report-redis/src/main/java/org/apache/dubbo/metadata/store/redis/RedisMetadataReport.java @@ -34,9 +34,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT; import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.metadata.identifier.MetadataIdentifier.META_DATA_STORE_TAG; /** diff --git a/dubbo-metadata/pom.xml b/dubbo-metadata/pom.xml new file mode 100644 index 0000000000..9a690ae58a --- /dev/null +++ b/dubbo-metadata/pom.xml @@ -0,0 +1,66 @@ + + + + org.apache.dubbo + dubbo-parent + ${revision} + ../pom.xml + + 4.0.0 + + dubbo-metadata + jar + + dubbo-metadata + The metadata module of Dubbo project + + + + + org.apache.dubbo + dubbo-config-api + ${project.parent.version} + true + + + + org.apache.dubbo + dubbo-rpc-api + ${project.parent.version} + true + + + + + org.apache.dubbo + dubbo-configcenter-zookeeper + ${project.parent.version} + test + + + + org.apache.curator + curator-test + test + + + + + \ No newline at end of file diff --git a/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java new file mode 100644 index 0000000000..f535a517da --- /dev/null +++ b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java @@ -0,0 +1,103 @@ +/* + * 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.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.configcenter.DynamicConfiguration; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import static java.util.Arrays.asList; +import static org.apache.dubbo.common.utils.StringUtils.isBlank; + +/** + * The {@link ServiceNameMapping} implementation based on {@link DynamicConfiguration} + */ +public class DynamicConfigurationServiceNameMapping implements ServiceNameMapping { + + private static final List IGNORED_SERVICE_INTERFACES = asList(MetadataService.class.getName()); + + private static final String SEPARATOR = ":"; + + private static final String EMPTY = ""; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public void map(String serviceInterface, String group, String version, String protocol) { + + if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) { + return; + } + + DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration(); + + // the Dubbo Service Key as group + // the service(application) name as key + // It does matter whatever the content is, we just need a record + String key = ApplicationModel.getApplication(); + String content = String.valueOf(System.currentTimeMillis()); + execute(() -> { + dynamicConfiguration.publishConfig(key, buildGroup(serviceInterface, group, version, protocol), content); + if (logger.isInfoEnabled()) { + logger.info(String.format("The Dubbo service key[%s] mapped to service name[%s] with content : %s", + key, group, content)); + } + }); + } + + @Override + public Set get(String serviceInterface, String group, String version, String protocol) { + + DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration(); + + String key = ApplicationModel.getApplication(); + Set serviceNames = new LinkedHashSet<>(); + execute(() -> { + Set keys = dynamicConfiguration.getConfigKeys(buildGroup(serviceInterface, group, version, protocol)); + serviceNames.addAll(keys); + }); + return Collections.unmodifiableSet(serviceNames); + } + + protected static String buildGroup(String serviceInterface, String group, String version, String protocol) { + StringBuilder groupBuilder = new StringBuilder(serviceInterface) + .append(SEPARATOR).append(defaultString(group)) + .append(SEPARATOR).append(defaultString(version)) + .append(SEPARATOR).append(defaultString(protocol)); + return groupBuilder.toString(); + } + + private static String defaultString(String value) { + return isBlank(value) ? EMPTY : value; + } + + private void execute(Runnable runnable) { + try { + runnable.run(); + } catch (Throwable e) { + if (logger.isWarnEnabled()) { + logger.warn(e.getMessage(), e); + } + } + } +} diff --git a/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/InMemoryLocalMetadataService.java b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/InMemoryLocalMetadataService.java new file mode 100644 index 0000000000..947a9fbd28 --- /dev/null +++ b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/InMemoryLocalMetadataService.java @@ -0,0 +1,192 @@ +/* + * 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.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static org.apache.dubbo.common.URL.buildKey; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; +import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty; + +/** + * The {@link LocalMetadataService} implementation stores the metadata of Dubbo services in memory locally when they + * exported. + * + * @see MetadataService + * @see LocalMetadataService + * @since 2.7.3 + */ +public class InMemoryLocalMetadataService implements LocalMetadataService { + + /** + * The class name of {@link MetadataService} + */ + static final String METADATA_SERVICE_CLASS_NAME = MetadataService.class.getName(); + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final Lock lock = new ReentrantLock(); + + // =================================== Registration =================================== // + + /** + * All exported {@link URL urls} {@link Map} whose key is the return value of {@link URL#getServiceKey()} method + * and value is the {@link List} of the {@link URL URLs} + */ + private ConcurrentMap> exportedServiceURLs = new ConcurrentHashMap<>(); + + // ==================================================================================== // + + // =================================== Subscription =================================== // + + /** + * The subscribed {@link URL urls} {@link Map} of {@link MetadataService}, + * whose key is the return value of {@link URL#getServiceKey()} method and value is the {@link List} of + * the {@link URL URLs} + */ + private final ConcurrentMap> subscribedServiceURLs = new ConcurrentHashMap<>(); + + // ==================================================================================== // + + @Override + public List getSubscribedURLs() { + return getAllUnmodifiableServiceURLs(subscribedServiceURLs); + } + + @Override + public List getExportedURLs(String serviceInterface, String group, String version, String protocol) { + if (ALL_SERVICE_INTERFACES.equals(serviceInterface)) { + return getAllUnmodifiableServiceURLs(exportedServiceURLs); + } + String serviceKey = buildKey(serviceInterface, group, version); + return unmodifiableList(getServiceURLs(exportedServiceURLs, serviceKey, protocol)); + } + + @Override + public boolean exportURL(URL url) { +// if (isMetadataServiceURL(url)) { // ignore MetadataService in the export phase +// return true; +// } + return addURL(exportedServiceURLs, url); + } + + @Override + public boolean unexportURL(URL url) { +// if (isMetadataServiceURL(url)) { // ignore MetadataService in the export phase +// return true; +// } + return removeURL(exportedServiceURLs, url); + } + + @Override + public boolean subscribeURL(URL url) { + return addURL(subscribedServiceURLs, url); + } + + @Override + public boolean unsubscribeURL(URL url) { + return removeURL(subscribedServiceURLs, url); + } + + private boolean addURL(Map> serviceURLs, URL url) { + return executeMutually(() -> { + List urls = serviceURLs.computeIfAbsent(url.getServiceKey(), s -> new LinkedList()); + if (!urls.contains(url)) { + return urls.add(url); + } + return false; + }); + } + + private boolean removeURL(Map> serviceURLs, URL url) { + return executeMutually(() -> { + List urls = serviceURLs.get(url.getServiceKey()); + if (isEmpty(urls)) { + return false; + } + return urls.remove(url); + }); + } + + private boolean executeMutually(Callable callable) { + boolean success = false; + try { + lock.lock(); + try { + success = callable.call(); + } catch (Exception e) { + if (logger.isErrorEnabled()) { + logger.error(e); + } + } + } finally { + lock.unlock(); + } + return success; + } + + private static List getServiceURLs(Map> exportedServiceURLs, String serviceKey, + String protocol) { + List serviceURLs = exportedServiceURLs.get(serviceKey); + + if (isEmpty(serviceURLs)) { + return emptyList(); + } + + return serviceURLs + .stream() + .filter(url -> isAcceptableProtocol(protocol, url)) + .map(URL::toFullString) + .collect(Collectors.toList()); + } + + private static boolean isAcceptableProtocol(String protocol, URL url) { + return protocol == null + || protocol.equals(url.getParameter(PROTOCOL_KEY)) + || protocol.equals(url.getProtocol()); + } + +// private static boolean isMetadataServiceURL(URL url) { +// String serviceInterface = url.getServiceInterface(); +// return METADATA_SERVICE_CLASS_NAME.equals(serviceInterface); +// } + + private static List getAllUnmodifiableServiceURLs(Map> serviceURLs) { + return unmodifiableList( + serviceURLs + .values() + .stream() + .flatMap(Collection::stream) + .map(URL::toFullString) + .collect(Collectors.toList())); + } +} diff --git a/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/LocalMetadataService.java b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/LocalMetadataService.java new file mode 100644 index 0000000000..172409524e --- /dev/null +++ b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/LocalMetadataService.java @@ -0,0 +1,88 @@ +/* + * 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.extension.ExtensionLoader; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; + +/** + * Local {@link MetadataService} that extends {@link MetadataService} and provides the modification, which is used for + * Dubbo's consumers and providers. + * + * @since 2.7.3 + */ +@SPI("default") +public interface LocalMetadataService extends MetadataService { + + /** + * Gets the current Dubbo Service name + * + * @return non-null + */ + @Override + default String serviceName() { + return ApplicationModel.getApplication(); + } + + /** + * Exports a {@link URL} + * + * @param url a {@link URL} + * @return If success , return true + */ + boolean exportURL(URL url); + + /** + * Unexports a {@link URL} + * + * @param url a {@link URL} + * @return If success , return true + */ + boolean unexportURL(URL url); + + /** + * Subscribes a {@link URL} + * + * @param url a {@link URL} + * @return If success , return true + */ + boolean subscribeURL(URL url); + + /** + * Unsubscribes a {@link URL} + * + * @param url a {@link URL} + * @return If success , return true + */ + boolean unsubscribeURL(URL url); + + + /** + * Get {@link ExtensionLoader#getDefaultExtension() the defautl extension} of {@link LocalMetadataService} + * + * @return non-null + * @see InMemoryLocalMetadataService + */ + public static LocalMetadataService getDefaultExtension() { + return getExtensionLoader(LocalMetadataService.class).getDefaultExtension(); + } + +} diff --git a/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataService.java b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataService.java new file mode 100644 index 0000000000..f73d5f6e8c --- /dev/null +++ b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataService.java @@ -0,0 +1,151 @@ +/* + * 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 java.util.List; +import java.util.stream.Collectors; + +import static java.util.stream.StreamSupport.stream; + +/** + * A framework interface of Dubbo Metadata Service defines the contract of Dubbo Services registartion and subscription + * between Dubbo service providers and its consumers. The implementationwill be exported as a normal Dubbo service that + * the clients would subscribe, whose version comes from the {@link #version()} method and group gets from + * {@link #serviceName()}, that means, The different Dubbo service(application) will export the different + * {@link MetadataService} that persists all the exported and subscribed metadata, they are present by + * {@link #getExportedURLs()} and {@link #getSubscribedURLs()} respectively. What's more, {@link MetadataService} + * also providers the fine-grain methods for the precise queries. + * + * @see LocalMetadataService + * @since 2.7.3 + */ +public interface MetadataService { + + /** + * The value of all service names + */ + String ALL_SERVICE_NAMES = "*"; + + /** + * The value of All service instances + */ + String ALL_SERVICE_INTERFACES = "*"; + + /** + * The contract version of {@link MetadataService}, the future update must make sure compatible. + */ + String VERSION = "1.0.0"; + + /** + * Gets the current Dubbo Service name + * + * @return non-null + */ + String serviceName(); + + /** + * Gets the version of {@link MetadataService} that always equals {@link #VERSION} + * + * @return non-null + * @see #VERSION + */ + default String version() { + return VERSION; + } + + /** + * the list of String that presents all Dubbo subscribed {@link URL urls} + * + * @return non-null read-only {@link List} + */ + List getSubscribedURLs(); + + /** + * Get the list of String that presents all Dubbo exported {@link URL urls} + * + * @return non-null read-only {@link List} + */ + default List getExportedURLs() { + return getExportedURLs(ALL_SERVICE_INTERFACES); + } + + /** + * Get the list of String that presents the specified Dubbo exported {@link URL urls} by the serviceInterface + * + * @param serviceInterface The class name of Dubbo service interface + * @return non-null read-only {@link List} + * @see URL + */ + default List getExportedURLs(String serviceInterface) { + return getExportedURLs(serviceInterface, null); + } + + /** + * Get the list of String that presents the specified Dubbo exported {@link URL urls} by the + * serviceInterface and group + * + * @param serviceInterface The class name of Dubbo service interface + * @param group the Dubbo Service Group (optional) + * @return non-null read-only {@link List} + * @see URL + */ + default List getExportedURLs(String serviceInterface, String group) { + return getExportedURLs(serviceInterface, group, null); + } + + /** + * Get the list of String that presents the specified Dubbo exported {@link URL urls} by the + * serviceInterface, group and version + * + * @param serviceInterface The class name of Dubbo service interface + * @param group the Dubbo Service Group (optional) + * @param version the Dubbo Service Version (optional) + * @return non-null read-only {@link List} + * @see URL + */ + default List getExportedURLs(String serviceInterface, String group, String version) { + return getExportedURLs(serviceInterface, group, version, null); + } + + /** + * Get the list of String that presents the specified Dubbo exported {@link URL urls} by the + * serviceInterface, group, version and protocol + * + * @param serviceInterface The class name of Dubbo service interface + * @param group the Dubbo Service Group (optional) + * @param version the Dubbo Service Version (optional) + * @param protocol the Dubbo Service Protocol (optional) + * @return non-null read-only {@link List} + * @see URL + */ + List getExportedURLs(String serviceInterface, String group, String version, String protocol); + + + /** + * Convert the multiple {@link URL urls} to a {@link List list} of {@link URL urls} + * + * @param urls the strings presents the {@link URL Dubbo URLs} + * @return non-null + */ + static List toURLs(Iterable urls) { + return stream(urls.spliterator(), false) + .map(URL::valueOf) + .collect(Collectors.toList()); + } +} diff --git a/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java new file mode 100644 index 0000000000..8bbe53c6d2 --- /dev/null +++ b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/MetadataServiceExporter.java @@ -0,0 +1,44 @@ +/* + * 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 java.util.List; + +/** + * The exporter of {@link MetadataService} + * + * @see MetadataService + * @see #export() + * @see #unexport() + * @since 2.7.3 + */ +public interface MetadataServiceExporter { + + /** + * Exports the {@link MetadataService} as a Dubbo service + * + * @return the exported {@link URL URLs} + */ + List export(); + + /** + * Unexports the {@link MetadataService} + */ + void unexport(); +} diff --git a/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java new file mode 100644 index 0000000000..a642f3d2a9 --- /dev/null +++ b/dubbo-metadata/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java @@ -0,0 +1,64 @@ +/* + * 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.extension.SPI; + +import java.util.Set; + +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; + +/** + * The interface for Dubbo service name Mapping + * + * @since 2.7.3 + */ +@SPI("default") +public interface ServiceNameMapping { + + /** + * Map the specified Dubbo service interface, group, version and protocol to current Dubbo service name + * + * @param serviceInterface the class name of Dubbo service interface + * @param group the group of Dubbo service interface (optional) + * @param version the version of Dubbo service interface version (optional) + * @param protocol the protocol of Dubbo service interface exported (optional) + */ + void map(String serviceInterface, String group, String version, String protocol); + + /** + * Get the service names from the specified Dubbo service interface, group, version and protocol + * + * @param serviceInterface the class name of Dubbo service interface + * @param group the group of Dubbo service interface (optional) + * @param version the version of Dubbo service interface version (optional) + * @param protocol the protocol of Dubbo service interface exported (optional) + * @return + */ + Set get(String serviceInterface, String group, String version, String protocol); + + + /** + * Get the default extension of {@link ServiceNameMapping} + * + * @return non-null {@link ServiceNameMapping} + * @see DynamicConfigurationServiceNameMapping + */ + static ServiceNameMapping getDefaultExtension() { + return getExtensionLoader(ServiceNameMapping.class).getDefaultExtension(); + } +} diff --git a/dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.LocalMetadataService b/dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.LocalMetadataService new file mode 100644 index 0000000000..2af06fde61 --- /dev/null +++ b/dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.LocalMetadataService @@ -0,0 +1 @@ +default=org.apache.dubbo.metadata.InMemoryLocalMetadataService \ No newline at end of file diff --git a/dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.ServiceNameMapping b/dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.ServiceNameMapping new file mode 100644 index 0000000000..3975068fc9 --- /dev/null +++ b/dubbo-metadata/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.ServiceNameMapping @@ -0,0 +1 @@ +default=org.apache.dubbo.metadata.DynamicConfigurationServiceNameMapping \ No newline at end of file diff --git a/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMappingTest.java b/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMappingTest.java new file mode 100644 index 0000000000..95e7a1689a --- /dev/null +++ b/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMappingTest.java @@ -0,0 +1,114 @@ +/* + * 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.Environment; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.configcenter.DynamicConfiguration; +import org.apache.dubbo.configcenter.DynamicConfigurationFactory; +import org.apache.dubbo.rpc.model.ApplicationModel; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.test.TestingServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Set; +import java.util.TreeSet; + +import static java.util.Arrays.asList; +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; +import static org.apache.dubbo.metadata.DynamicConfigurationServiceNameMapping.buildGroup; +import static org.apache.dubbo.metadata.ServiceNameMapping.getDefaultExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link DynamicConfigurationServiceNameMapping} Test + * + * @since 2.7.3 + */ +public class DynamicConfigurationServiceNameMappingTest { + + private static CuratorFramework client; + + private static URL configUrl; + private static int zkServerPort = NetUtils.getAvailablePort(); + private static TestingServer zkServer; + + private final ServiceNameMapping serviceNameMapping = getDefaultExtension(); + + @BeforeAll + public static void setUp() throws Exception { + + zkServer = new TestingServer(zkServerPort, true); + + client = CuratorFrameworkFactory.newClient("localhost:" + zkServerPort, 60 * 1000, 60 * 1000, + new ExponentialBackoffRetry(1000, 3)); + + client.start(); + + configUrl = URL.valueOf("zookeeper://localhost:" + zkServerPort); + + DynamicConfiguration configuration = getExtensionLoader(DynamicConfigurationFactory.class) + .getExtension(configUrl.getProtocol()) + .getDynamicConfiguration(configUrl); + + Environment.getInstance().setDynamicConfiguration(configuration); + } + + @AfterAll + public static void tearDown() throws Exception { + zkServer.stop(); + } + + @Test + public void testBuildGroup() { + assertEquals("test:::", buildGroup("test", null, null, null)); + assertEquals("test:default::", buildGroup("test", "default", null, null)); + assertEquals("test:default:1.0.0:", buildGroup("test", "default", "1.0.0", null)); + assertEquals("test:default:1.0.0:dubbo", buildGroup("test", "default", "1.0.0", "dubbo")); + } + + @Test + public void testMapAndGet() { + + String serviceName = "test"; + String serviceName2 = "test2"; + + ApplicationModel.setApplication(serviceName); + + String serviceInterface = "org.apache.dubbo.service.UserService"; + String group = null; + String version = null; + String protocol = null; + + serviceNameMapping.map(serviceInterface, group, version, protocol); + + ApplicationModel.setApplication(serviceName2); + + serviceNameMapping.map(serviceInterface, group, version, protocol); + + Set serviceNames = serviceNameMapping.get(serviceInterface, group, version, protocol); + + assertEquals(new TreeSet(asList(serviceName, serviceName2)), serviceNames); + + } +} diff --git a/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/InMemoryLocalMetadataServiceTest.java b/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/InMemoryLocalMetadataServiceTest.java new file mode 100644 index 0000000000..406e4aeb98 --- /dev/null +++ b/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/InMemoryLocalMetadataServiceTest.java @@ -0,0 +1,140 @@ +/* + * 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.rpc.model.ApplicationModel; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.apache.dubbo.common.URL.valueOf; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_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 InMemoryLocalMetadataService} Test + * + * @since 2.7.3 + */ +public class InMemoryLocalMetadataServiceTest { + + private LocalMetadataService metadataService = new InMemoryLocalMetadataService(); + + private static final String TEST_SERVICE = "org.apache.dubbo.test.TestService"; + + private static final URL BASE_URL = valueOf("dubbo://127.0.0.1:20880/" + TEST_SERVICE); + private static final URL REST_BASE_URL = valueOf("rest://127.0.0.1:20880/" + TEST_SERVICE); + private static final URL BASE_URL_GROUP = BASE_URL.addParameter(GROUP_KEY, "test"); + private static final URL BASE_URL_GROUP_AND_VERSION = BASE_URL_GROUP.addParameter(VERSION_KEY, "1.0.0"); + private static final URL BASE_URL_GROUP_AND_VERSION_AND_PROTOCOL = BASE_URL_GROUP_AND_VERSION.addParameter(PROTOCOL_KEY, "rest"); + + @BeforeAll + public static void init() { + ApplicationModel.setApplication("test"); + } + + @Test + public void testServiceName() { + assertEquals("test", metadataService.serviceName()); + } + + @Test + public void testVersion() { + assertEquals("1.0.0", MetadataService.VERSION); + assertEquals("1.0.0", metadataService.version()); + } + + @Test + public void testGetExportedURLs() { + + assertTrue(metadataService.exportURL(BASE_URL)); + List exportedURLs = metadataService.getExportedURLs(TEST_SERVICE); + assertEquals(1, exportedURLs.size()); + assertEquals(asList(BASE_URL.toFullString()), exportedURLs); + assertTrue(metadataService.unexportURL(BASE_URL)); + + assertTrue(metadataService.exportURL(BASE_URL)); + assertFalse(metadataService.exportURL(BASE_URL)); + + assertTrue(metadataService.exportURL(BASE_URL_GROUP)); + assertTrue(metadataService.exportURL(BASE_URL_GROUP_AND_VERSION)); + + exportedURLs = metadataService.getExportedURLs(TEST_SERVICE); + assertEquals(asList(BASE_URL.toFullString()), exportedURLs); + assertEquals(asList( + BASE_URL.toFullString(), + BASE_URL_GROUP.toFullString(), + BASE_URL_GROUP_AND_VERSION.toFullString()), metadataService.getExportedURLs()); + + assertTrue(metadataService.exportURL(REST_BASE_URL)); + exportedURLs = metadataService.getExportedURLs(TEST_SERVICE); + assertEquals(asList(BASE_URL.toFullString(), REST_BASE_URL.toFullString()), exportedURLs); + + metadataService.exportURL(BASE_URL_GROUP_AND_VERSION_AND_PROTOCOL); + + exportedURLs = metadataService.getExportedURLs(TEST_SERVICE, "test", "1.0.0", "rest"); + + assertEquals(asList(BASE_URL_GROUP_AND_VERSION_AND_PROTOCOL.toFullString()), exportedURLs); + } + + @Test + public void testGetSubscribedURLs() { + assertTrue(metadataService.subscribeURL(BASE_URL)); + assertFalse(metadataService.subscribeURL(BASE_URL)); + + assertTrue(metadataService.subscribeURL(BASE_URL_GROUP)); + assertTrue(metadataService.subscribeURL(BASE_URL_GROUP_AND_VERSION)); + assertTrue(metadataService.subscribeURL(REST_BASE_URL)); + + List subscribedURLs = metadataService.getSubscribedURLs(); + assertEquals(4, subscribedURLs.size()); + assertEquals(asList( + BASE_URL.toFullString(), + REST_BASE_URL.toFullString(), + BASE_URL_GROUP.toFullString(), + BASE_URL_GROUP_AND_VERSION.toFullString()), subscribedURLs); + + assertTrue(metadataService.unsubscribeURL(REST_BASE_URL)); + subscribedURLs = metadataService.getSubscribedURLs(); + assertEquals(3, subscribedURLs.size()); + assertEquals(asList( + BASE_URL.toFullString(), + BASE_URL_GROUP.toFullString(), + BASE_URL_GROUP_AND_VERSION.toFullString()), subscribedURLs); + + assertTrue(metadataService.unsubscribeURL(BASE_URL_GROUP)); + subscribedURLs = metadataService.getSubscribedURLs(); + assertEquals(2, subscribedURLs.size()); + assertEquals(asList( + BASE_URL.toFullString(), + BASE_URL_GROUP_AND_VERSION.toFullString()), subscribedURLs); + + assertTrue(metadataService.unsubscribeURL(BASE_URL_GROUP_AND_VERSION)); + subscribedURLs = metadataService.getSubscribedURLs(); + assertEquals(1, subscribedURLs.size()); + assertEquals(asList( + BASE_URL.toFullString()), subscribedURLs); + } +} \ No newline at end of file diff --git a/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.java b/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.java new file mode 100644 index 0000000000..508a959873 --- /dev/null +++ b/dubbo-metadata/src/test/java/org/apache/dubbo/metadata/LocalMetadataServiceTest.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; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link LocalMetadataService} Test + * + * @since 2.7.3 + */ +public class LocalMetadataServiceTest { + + @Test + public void testDefaultExtension() { + assertEquals(InMemoryLocalMetadataService.class, LocalMetadataService.getDefaultExtension().getClass()); + } +} diff --git a/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java b/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java index 2216e27306..5f3bb7013b 100644 --- a/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java +++ b/dubbo-monitor/dubbo-monitor-api/src/main/java/org/apache/dubbo/monitor/support/MonitorFilter.java @@ -40,12 +40,12 @@ import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SIDE; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; import static org.apache.dubbo.monitor.Constants.COUNT_PROTOCOL; -import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; import static org.apache.dubbo.rpc.Constants.INPUT_KEY; import static org.apache.dubbo.rpc.Constants.OUTPUT_KEY; /** diff --git a/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitorFactory.java b/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitorFactory.java index 591296fa44..94dd5f2d19 100644 --- a/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitorFactory.java +++ b/dubbo-monitor/dubbo-monitor-default/src/main/java/org/apache/dubbo/monitor/dubbo/DubboMonitorFactory.java @@ -26,8 +26,8 @@ import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Protocol; import org.apache.dubbo.rpc.ProxyFactory; -import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.remoting.Constants.CHECK_KEY; import static org.apache.dubbo.rpc.Constants.REFERENCE_FILTER_KEY; diff --git a/dubbo-registry/dubbo-registry-api/pom.xml b/dubbo-registry/dubbo-registry-api/pom.xml index 5a707d0686..44eb895b27 100644 --- a/dubbo-registry/dubbo-registry-api/pom.xml +++ b/dubbo-registry/dubbo-registry-api/pom.xml @@ -14,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> - + 4.0.0 org.apache.dubbo @@ -55,6 +56,18 @@ + + org.apache.dubbo + dubbo-event + ${project.parent.version} + + + + org.apache.dubbo + dubbo-metadata + ${project.parent.version} + + org.apache.curator curator-framework diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java new file mode 100644 index 0000000000..939571992a --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceInstance.java @@ -0,0 +1,142 @@ +/* + * 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; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * The default implementation of {@link ServiceInstance}. + * + * @since 2.7.3 + */ +public class DefaultServiceInstance implements ServiceInstance { + + private final String id; + + private final String serviceName; + + private final String host; + + private Integer port; + + private boolean enabled; + + private boolean healthy; + + private Map metadata = new HashMap<>(); + + public DefaultServiceInstance(String id, String serviceName, String host, Integer port) { + if (port != null && port.intValue() < 1) { + throw new IllegalArgumentException("The port must be greater than zero!"); + } + this.id = id; + this.serviceName = serviceName; + this.host = host; + this.port = port; + this.enabled = true; + this.healthy = true; + } + + public DefaultServiceInstance(String serviceName, String host, Integer port) { + this(null, serviceName, host, port); + } + + @Override + public String getId() { + return id; + } + + @Override + public String getServiceName() { + return serviceName; + } + + @Override + public String getHost() { + return host; + } + + public void setPort(Integer port) { + this.port = port; + } + + @Override + public Integer getPort() { + return port; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public boolean isHealthy() { + return healthy; + } + + public void setHealthy(boolean healthy) { + this.healthy = healthy; + } + + @Override + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DefaultServiceInstance)) return false; + DefaultServiceInstance that = (DefaultServiceInstance) o; + return isEnabled() == that.isEnabled() && + isHealthy() == that.isHealthy() && + Objects.equals(getId(), that.getId()) && + Objects.equals(getServiceName(), that.getServiceName()) && + Objects.equals(getHost(), that.getHost()) && + Objects.equals(getPort(), that.getPort()) && + Objects.equals(getMetadata(), that.getMetadata()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getServiceName(), getHost(), getPort(), isEnabled(), isHealthy(), getMetadata()); + } + + @Override + public String toString() { + return "DefaultServiceInstance{" + + "id='" + id + '\'' + + ", serviceName='" + serviceName + '\'' + + ", host='" + host + '\'' + + ", port=" + port + + ", enabled=" + enabled + + ", healthy=" + healthy + + ", metadata=" + metadata + + '}'; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java new file mode 100644 index 0000000000..d1fc7927d4 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java @@ -0,0 +1,293 @@ +/* + * 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; + +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.Page; +import org.apache.dubbo.event.Event; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStartedEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStartingEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStoppedEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStoppingEvent; +import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstancePreUnregisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstanceRegisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstanceUnregisteredEvent; +import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.util.Optional.empty; +import static java.util.Optional.of; + +/** + * The decorating implementation of {@link ServiceDiscovery} to publishe the {@link Event Dubbo event} when some actions are + * executing, including: + *
      + *
    • Lifecycle actions:
    • + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      ActionbeforeAfter
      {@link #START_ACTION start}{@link ServiceDiscoveryStartingEvent}{@link ServiceDiscoveryStartedEvent}
      {@link #STOP_ACTION stop}{@link ServiceDiscoveryStoppingEvent}{@link ServiceDiscoveryStoppedEvent}
      + *
    • Registration actions:
    • + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      ActionbeforeAfter
      {@link #REGISTER_ACTION register}{@link ServiceInstancePreRegisteredEvent}{@link ServiceInstanceRegisteredEvent}
      {@link #UPDATE_ACTION update}N/AN/A
      {@link #UNREGISTER_ACTION unregister}N/AN/A
      + *
    + * + * @see ServiceDiscovery + * @see ServiceDiscoveryStartingEvent + * @see ServiceDiscoveryStartedEvent + * @see ServiceInstancePreRegisteredEvent + * @see ServiceInstanceRegisteredEvent + * @see ServiceDiscoveryStoppingEvent + * @see ServiceDiscoveryStoppedEvent + * @since 2.7.3 + */ +class EventPublishingServiceDiscovery implements ServiceDiscovery { + + /** + * @see ServiceInstancePreRegisteredEvent + * @see ServiceInstanceRegisteredEvent + */ + protected static final String REGISTER_ACTION = "register"; + + protected static final String UPDATE_ACTION = "update"; + + protected static final String UNREGISTER_ACTION = "unregister"; + + /** + * @see ServiceDiscoveryStartingEvent + * @see ServiceDiscoveryStartedEvent + */ + protected static final String START_ACTION = "start"; + + /** + * @see ServiceDiscoveryStoppingEvent + * @see ServiceDiscoveryStoppedEvent + */ + protected static final String STOP_ACTION = "stop"; + + protected final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + + protected final AtomicBoolean started = new AtomicBoolean(false); + + protected final AtomicBoolean stopped = new AtomicBoolean(false); + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final ServiceDiscovery serviceDiscovery; + + protected EventPublishingServiceDiscovery(ServiceDiscovery serviceDiscovery) { + if (serviceDiscovery == null) { + throw new NullPointerException("The ServiceDiscovery argument must not be null!"); + } + this.serviceDiscovery = serviceDiscovery; + } + + @Override + public final void register(ServiceInstance serviceInstance) throws RuntimeException { + + requireStarted(REGISTER_ACTION); + requireNotStopped(REGISTER_ACTION); + + executeWithEvents( + of(new ServiceInstancePreRegisteredEvent(serviceDiscovery, serviceInstance)), + () -> serviceDiscovery.register(serviceInstance), + of(new ServiceInstanceRegisteredEvent(serviceDiscovery, serviceInstance)) + ); + } + + @Override + public final void update(ServiceInstance serviceInstance) throws RuntimeException { + + requireStarted(UPDATE_ACTION); + requireNotStopped(UPDATE_ACTION); + + executeWithEvents( + empty(), + () -> serviceDiscovery.update(serviceInstance), + empty() + ); + } + + @Override + public final void unregister(ServiceInstance serviceInstance) throws RuntimeException { + + requireStarted(UNREGISTER_ACTION); + requireNotStopped(UNREGISTER_ACTION); + + executeWithEvents( + of(new ServiceInstancePreUnregisteredEvent(this, serviceInstance)), + () -> serviceDiscovery.unregister(serviceInstance), + of(new ServiceInstanceUnregisteredEvent(this, serviceInstance)) + ); + } + + @Override + public Set getServices() { + return serviceDiscovery.getServices(); + } + + @Override + public List getInstances(String serviceName) throws NullPointerException { + return serviceDiscovery.getInstances(serviceName); + } + + @Override + public Page getInstances(String serviceName, int offset, int pageSize) throws NullPointerException, IllegalArgumentException { + return serviceDiscovery.getInstances(serviceName, offset, pageSize); + } + + @Override + public Page getInstances(String serviceName, int offset, int pageSize, boolean healthyOnly) throws NullPointerException, IllegalArgumentException { + return serviceDiscovery.getInstances(serviceName, offset, pageSize, healthyOnly); + } + + @Override + public Map> getInstances(Iterable serviceNames, int offset, int requestSize) throws NullPointerException, IllegalArgumentException { + return serviceDiscovery.getInstances(serviceNames, offset, requestSize); + } + + @Override + public String toString() { + return serviceDiscovery.toString(); + } + + @Override + public void addServiceInstancesChangedListener(String serviceName, ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException { + serviceDiscovery.addServiceInstancesChangedListener(serviceName, listener); + } + + @Override + public final void start() { + + requireNotStopped(START_ACTION); + + if (isStarted()) { + if (logger.isWarnEnabled()) { + logger.warn("It's ignored to start current ServiceDiscovery, because it has been started."); + } + return; + } + + executeWithEvents( + of(new ServiceDiscoveryStartingEvent(serviceDiscovery)), + serviceDiscovery::start, + of(new ServiceDiscoveryStartedEvent(serviceDiscovery)) + ); + + // doesn't start -> started + started.compareAndSet(false, true); + } + + @Override + public final void stop() { + + requireStarted(STOP_ACTION); + + if (isStopped()) { + if (logger.isWarnEnabled()) { + logger.warn("It's ignored to stop current ServiceDiscovery, because it has been stopped."); + } + return; + } + + executeWithEvents( + of(new ServiceDiscoveryStoppingEvent(serviceDiscovery)), + serviceDiscovery::stop, + of(new ServiceDiscoveryStoppedEvent(serviceDiscovery)) + ); + + // doesn't stop -> stopped + stopped.compareAndSet(false, true); + } + + protected final void executeWithEvents(Optional beforeEvent, + Runnable action, + Optional afterEvent) { + beforeEvent.ifPresent(eventDispatcher::dispatch); + action.run(); + afterEvent.ifPresent(eventDispatcher::dispatch); + } + + public final boolean isStarted() { + return started.get(); + } + + public final boolean isStopped() { + return stopped.get(); + } + + protected void requireStarted(String action) throws IllegalStateException { + if (!isStarted()) { + throw new IllegalStateException("The action[" + action + "] is rejected, because the ServiceDiscovery is not started yet."); + } + } + + protected void requireNotStopped(String action) throws IllegalStateException { + if (isStopped()) { + throw new IllegalStateException("The action[" + action + "] is rejected, because the ServiceDiscovery is stopped already."); + } + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryFactory.java new file mode 100644 index 0000000000..dc5a614c7f --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryFactory.java @@ -0,0 +1,47 @@ +package org.apache.dubbo.registry.client;/* + * 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. + */ + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.SPI; + +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; + +/** + * The factory class to create an instance of {@link ServiceDiscoveryFactory} based on Event-Publishing as the default + * {@link SPI} implementation + * + * @see ServiceDiscoveryFactory + * @see EventPublishingServiceDiscovery + * @see ServiceDiscovery + * @since 2.7.3 + */ +public class EventPublishingServiceDiscoveryFactory implements ServiceDiscoveryFactory { + + private static final Class FACTORY_CLASS = ServiceDiscoveryFactory.class; + + @Override + public ServiceDiscovery create(URL connectionURL) { + String protocol = connectionURL.getProtocol(); + ServiceDiscoveryFactory serviceDiscoveryFactory = loadFactoryByProtocol(protocol); + ServiceDiscovery originalServiceDiscovery = serviceDiscoveryFactory.create(connectionURL); + return new EventPublishingServiceDiscovery(originalServiceDiscovery); + } + + protected ServiceDiscoveryFactory loadFactoryByProtocol(String protocol) { + return getExtensionLoader(FACTORY_CLASS).getExtension(protocol); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java new file mode 100644 index 0000000000..947dd56cd7 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java @@ -0,0 +1,201 @@ +/* + * 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; + +import org.apache.dubbo.common.utils.Page; +import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; + +/** + * The common operations of Service Discovery + * + * @since 2.7.3 + */ +public interface ServiceDiscovery { + + // ==================================== Lifecycle ==================================== // + + /** + * Starts the ServiceRegistry. This is a lifecycle method. + */ + void start(); + + /** + * Stops the ServiceRegistry. This is a lifecycle method. + */ + void stop(); + + // ==================================================================================== // + + // =================================== Registration =================================== // + + /** + * Registers an instance of {@link ServiceInstance}. + * + * @param serviceInstance an instance of {@link ServiceInstance} to be registered + * @throws RuntimeException if failed + */ + void register(ServiceInstance serviceInstance) throws RuntimeException; + + /** + * Updates the registered {@link ServiceInstance}. + * + * @param serviceInstance the registered {@link ServiceInstance} + * @throws RuntimeException if failed + */ + void update(ServiceInstance serviceInstance) throws RuntimeException; + + /** + * Unregisters an instance of {@link ServiceInstance}. + * + * @param serviceInstance an instance of {@link ServiceInstance} to be deregistered + * @throws RuntimeException if failed + */ + void unregister(ServiceInstance serviceInstance) throws RuntimeException; + + // ==================================================================================== // + + // ==================================== Discovery ===================================== // + + /** + * Get the default size of pagination query + * + * @return the default value is 100 + */ + default int getDefaultPageSize() { + return 100; + } + + /** + * Gets all service names + * + * @return non-null read-only {@link Set} + */ + Set getServices(); + + /** + * Gets all {@link ServiceInstance service instances} by the specified service name. + * + * @param serviceName the service name + * @return non-null {@link List} + * @throws NullPointerException if serviceName is null is null + */ + default List getInstances(String serviceName) throws NullPointerException { + + List allInstances = new LinkedList<>(); + + int offset = 0; + + int pageSize = getDefaultPageSize(); + + Page page = getInstances(serviceName, offset, pageSize); + + allInstances.addAll(page.getData()); + + while (page.hasNext()) { + offset += page.getDataSize(); + page = getInstances(serviceName, offset, pageSize); + allInstances.addAll(page.getData()); + } + + return unmodifiableList(allInstances); + } + + /** + * Gets the {@link Page pagination} of {@link ServiceInstance service instances} by the specified service name. + * It's equal to {@link #getInstances(String, int, int, boolean)} with healthyOnly == true + * + * @param serviceName the service name + * @param offset the offset of request , the number "0" indicates first page + * @param pageSize the number of request, the {@link Integer#MAX_VALUE max value} indicates the range is unlimited + * @return non-null {@link Page} object + * @throws NullPointerException if serviceName is null is null + * @throws IllegalArgumentException if offset or pageSize is negative number + * @throws UnsupportedOperationException if not supported + */ + default Page getInstances(String serviceName, int offset, int pageSize) throws NullPointerException, + IllegalArgumentException { + return getInstances(serviceName, offset, pageSize, false); + } + + /** + * Get the {@link Page pagination} of {@link ServiceInstance service instances} by the specified service name. + * If healthyOnly == true, filter healthy instances only. + * + * @param serviceName the service name + * @param offset the offset of request , the number "0" indicates first page + * @param pageSize the number of request, the {@link Integer#MAX_VALUE max value} indicates the range is unlimited + * @param healthyOnly if true , filter healthy instances only + * @return non-null {@link Page} object + * @throws NullPointerException if serviceName is null is null + * @throws IllegalArgumentException if offset or pageSize is negative number + * @throws UnsupportedOperationException if not supported + */ + default Page getInstances(String serviceName, int offset, int pageSize, boolean healthyOnly) throws + NullPointerException, IllegalArgumentException, UnsupportedOperationException { + throw new UnsupportedOperationException("Current implementation does not support pagination query method."); + } + + /** + * batch-get all {@link ServiceInstance service instances} by the specified service names + * + * @param serviceNames the multiple service names + * @param offset the offset of request , the number "0" indicates first page + * @param requestSize the number of request, the {@link Integer#MAX_VALUE max value} indicates the range is unlimited + * @return non-null read-only {@link Map} whose key is the service name and value is + * the {@link Page pagination} of {@link ServiceInstance service instances} + * @throws NullPointerException if serviceName is null is null + * @throws IllegalArgumentException if offset or requestSize is negative number + * @throws UnsupportedOperationException if not supported + */ + default Map> getInstances(Iterable serviceNames, int offset, int requestSize) throws + NullPointerException, IllegalArgumentException { + Map> instances = new LinkedHashMap<>(); + for (String serviceName : serviceNames) { + instances.put(serviceName, getInstances(serviceName, offset, requestSize)); + } + return unmodifiableMap(instances); + } + + /** + * Add an instance of {@link ServiceInstancesChangedListener} for specified service + * + * @param serviceName the service name + * @param listener an instance of {@link ServiceInstancesChangedListener} + * @throws NullPointerException + * @throws IllegalArgumentException + */ + void addServiceInstancesChangedListener(String serviceName, ServiceInstancesChangedListener listener) + throws NullPointerException, IllegalArgumentException; + + // ==================================================================================== // + + /** + * A human-readable description of the implementation + * + * @return The description. + */ + String toString(); +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.java new file mode 100644 index 0000000000..f7e7cefbc1 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactory.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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.SPI; + +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; + +/** + * The Factory interface to create an instance of {@link ServiceDiscovery} + * + * @see ServiceDiscovery + * @since 2.7.3 + */ +@SPI("event-publishing") +public interface ServiceDiscoveryFactory { + + /** + * Creates an instance of {@link ServiceDiscovery}. + * + * @param connectionURL the {@link URL connection url} + * @return an instance of {@link ServiceDiscovery} + */ + ServiceDiscovery create(URL connectionURL); + + /** + * Get the default extension of {@link ServiceDiscoveryFactory} + * + * @return non-null + */ + static ServiceDiscoveryFactory getDefaultExtension() { + return getExtensionLoader(ServiceDiscoveryFactory.class).getDefaultExtension(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstance.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstance.java new file mode 100644 index 0000000000..bcdcfd29e6 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstance.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.registry.client; + +import java.util.Map; + +/** + * The model class of an instance of a service, which is used for service registration and discovery. + *

    + * + * @since 2.7.3 + */ +public interface ServiceInstance { + + /** + * The id of the registered service instance. + * + * @return nullable + */ + String getId(); + + /** + * The name of service that current instance belongs to. + * + * @return non-null + */ + String getServiceName(); + + /** + * The hostname of the registered service instance. + * + * @return non-null + */ + String getHost(); + + /** + * The port of the registered service instance. + * + * @return the positive integer if present + */ + Integer getPort(); + + /** + * The enable status of the registered service instance. + * + * @return if true, indicates current instance is enabled, or disable, the client should remove this one. + * The default value is true + */ + default boolean isEnabled() { + return true; + } + + /** + * The registered service instance is health or not. + * + * @return if true, indicates current instance is enabled, or disable, the client may ignore this one. + * The default value is true + */ + default boolean isHealthy() { + return true; + } + + /** + * The key / value pair metadata associated with the service instance. + * + * @return non-null, mutable and unsorted {@link Map} + */ + Map getMetadata(); + + /** + * @return the hash code of current instance. + */ + int hashCode(); + + /** + * @param another another {@link ServiceInstance} + * @return if equals , return true, or false + */ + boolean equals(Object another); +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java new file mode 100644 index 0000000000..976eb1aafc --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.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.registry.client; + +import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; +import org.apache.dubbo.registry.client.event.listener.CustomizableServiceInstanceListener; + +/** + * The interface to customize {@link ServiceInstance the service instance} on {@link ServiceInstancePreRegisteredEvent} + * + * @see CustomizableServiceInstanceListener + * @see ServiceInstancePreRegisteredEvent + * @see ServiceInstance#getMetadata() + * @since 2.7.3 + */ +public interface ServiceInstanceCustomizer { + + /** + * Customizes {@link ServiceInstance the service instance} + * + * @param serviceInstance {@link ServiceInstance the service instance} + */ + void customize(ServiceInstance serviceInstance); +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceMetadataCustomizer.java new file mode 100644 index 0000000000..d2077b6338 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceMetadataCustomizer.java @@ -0,0 +1,73 @@ +/* + * 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; + +import java.util.Map; + +import static org.apache.dubbo.common.utils.StringUtils.isBlank; + +/** + * The abstract class to customize {@link ServiceInstance#getMetadata()} the service instances' metadata} + * + * @see ServiceInstance#getMetadata() + * @see ServiceInstanceCustomizer + * @since 2.7.3 + */ +public abstract class ServiceInstanceMetadataCustomizer implements ServiceInstanceCustomizer { + + @Override + public final void customize(ServiceInstance serviceInstance) { + + Map metadata = serviceInstance.getMetadata(); + + String key = buildMetadataKey(serviceInstance); + String value = buildMetadataValue(serviceInstance); + + if (!isBlank(key) && !isBlank(value)) { + String existedValue = metadata.get(key); + boolean put = existedValue == null || isOverride(); + if (put) { + metadata.put(key, value); + } + } + } + + /** + * Build the key of metadata + * + * @param serviceInstance the instance of {@link ServiceInstance} + * @return non-null key + */ + protected abstract String buildMetadataKey(ServiceInstance serviceInstance); + + /** + * Build the value of metadata + * + * @param serviceInstance the instance of {@link ServiceInstance} + * @return non-null value + */ + protected abstract String buildMetadataValue(ServiceInstance serviceInstance); + + /** + * Is override {@link ServiceInstance#getMetadata()} the service instances' metadata} or not + * + * @return default is false + */ + protected boolean isOverride() { + return false; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartedEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartedEvent.java new file mode 100644 index 0000000000..f44c108c09 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartedEvent.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.event; + +import org.apache.dubbo.event.Event; +import org.apache.dubbo.registry.client.ServiceDiscovery; + +/** + * An event raised after the {@link ServiceDiscovery Service Discovery} started + * + * @see ServiceDiscovery#start() + * @since 2.7.3 + */ +public class ServiceDiscoveryStartedEvent extends Event { + + /** + * Constructs a prototypical Event. + * + * @param serviceDiscovery The instance of {@link ServiceDiscovery} as source + * @throws IllegalArgumentException if source is null. + */ + public ServiceDiscoveryStartedEvent(ServiceDiscovery serviceDiscovery) { + super(serviceDiscovery); + } + + /** + * Get the instance of {@link ServiceDiscovery} as source + * + * @return the instance of {@link ServiceDiscovery} as source + */ + public ServiceDiscovery getServiceDiscovery() { + return (ServiceDiscovery) getSource(); + } + +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartingEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartingEvent.java new file mode 100644 index 0000000000..63fd88c76f --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStartingEvent.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.registry.client.event; + +import org.apache.dubbo.event.Event; +import org.apache.dubbo.registry.client.ServiceDiscovery; + +/** + * An event raised when the {@link ServiceDiscovery Service Discovery} is starting. + * + * @see ServiceDiscovery#start + * @since 2.7.3 + */ +public class ServiceDiscoveryStartingEvent extends Event { + + /** + * Constructs a prototypical Event. + * + * @param serviceDiscovery The instance of {@link ServiceDiscovery} as source + * @throws IllegalArgumentException if source is null. + */ + public ServiceDiscoveryStartingEvent(ServiceDiscovery serviceDiscovery) { + super(serviceDiscovery); + } + + /** + * Get the instance of {@link ServiceDiscovery} as source + * + * @return the instance of {@link ServiceDiscovery} as source + */ + public ServiceDiscovery getServiceDiscovery() { + return (ServiceDiscovery) getSource(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppedEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppedEvent.java new file mode 100644 index 0000000000..97ff0a9a63 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppedEvent.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.registry.client.event; + +import org.apache.dubbo.event.Event; +import org.apache.dubbo.registry.client.ServiceDiscovery; + +/** + * An event raised after the {@link ServiceDiscovery Service Discovery} stopped. + * + * @see ServiceDiscovery#stop() + * @since 2.7.3 + */ +public class ServiceDiscoveryStoppedEvent extends Event { + + /** + * Constructs a prototypical Event. + * + * @param serviceDiscovery The instance of {@link ServiceDiscovery} as source + * @throws IllegalArgumentException if source is null. + */ + public ServiceDiscoveryStoppedEvent(ServiceDiscovery serviceDiscovery) { + super(serviceDiscovery); + } + + /** + * Get the instance of {@link ServiceDiscovery} as source + * + * @return the instance of {@link ServiceDiscovery} as source + */ + public ServiceDiscovery getServiceDiscovery() { + return (ServiceDiscovery) getSource(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppingEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppingEvent.java new file mode 100644 index 0000000000..8c827c2693 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceDiscoveryStoppingEvent.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.registry.client.event; + +import org.apache.dubbo.event.Event; +import org.apache.dubbo.registry.client.ServiceDiscovery; + +/** + * An event raised when the {@link ServiceDiscovery Service Discovery} is stopping. + * + * @see ServiceDiscovery#stop() + * @since 2.7.3 + */ +public class ServiceDiscoveryStoppingEvent extends Event { + + /** + * Constructs a prototypical Event. + * + * @param serviceDiscovery The instance of {@link ServiceDiscovery} as source + * @throws IllegalArgumentException if source is null. + */ + public ServiceDiscoveryStoppingEvent(ServiceDiscovery serviceDiscovery) { + super(serviceDiscovery); + } + + /** + * Get the instance of {@link ServiceDiscovery} as source + * + * @return the instance of {@link ServiceDiscovery} as source + */ + public ServiceDiscovery getServiceDiscovery() { + return (ServiceDiscovery) getSource(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceEvent.java new file mode 100644 index 0000000000..2533641d16 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceEvent.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.registry.client.event; + +import org.apache.dubbo.event.Event; +import org.apache.dubbo.registry.client.ServiceInstance; + +/** + * The {@link Event Dubbo event} for {@link ServiceInstance an service instance} + * + * @since 2.7.3 + */ +public abstract class ServiceInstanceEvent extends Event { + + private final ServiceInstance serviceInstance; + + /** + * @param serviceInstance {@link ServiceInstance an service instance} + */ + public ServiceInstanceEvent(Object source, ServiceInstance serviceInstance) { + super(source); + this.serviceInstance = serviceInstance; + } + + /** + * Get current {@link ServiceInstance service instance} + * + * @return current {@link ServiceInstance service instance} + */ + public ServiceInstance getServiceInstance() { + return serviceInstance; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.java new file mode 100644 index 0000000000..463d197093 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.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.registry.client.event; + +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; + + +/** + * An event raised before a {@link ServiceInstance service instance} + * {@link ServiceDiscovery#register(ServiceInstance) registered} + * + * @since 2.7.3 + */ +public class ServiceInstancePreRegisteredEvent extends ServiceInstanceEvent { + + public ServiceInstancePreRegisteredEvent(Object source, ServiceInstance serviceInstance) { + super(source, serviceInstance); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.java new file mode 100644 index 0000000000..dc7aa058ae --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.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.registry.client.event; + +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; + + +/** + * An event raised before a {@link ServiceInstance service instance} + * {@link ServiceDiscovery#unregister(ServiceInstance) unregistered} + * + * @since 2.7.3 + */ +public class ServiceInstancePreUnregisteredEvent extends ServiceInstanceEvent { + + public ServiceInstancePreUnregisteredEvent(Object source, ServiceInstance serviceInstance) { + super(source, serviceInstance); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.java new file mode 100644 index 0000000000..3617d0ef02 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.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.registry.client.event; + +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; + + +/** + * An event raised after a {@link ServiceInstance service instance} + * {@link ServiceDiscovery#register(ServiceInstance) registered} + * + * @since 2.7.3 + */ +public class ServiceInstanceRegisteredEvent extends ServiceInstanceEvent { + + public ServiceInstanceRegisteredEvent(Object source, ServiceInstance serviceInstance) { + super(source, serviceInstance); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.java new file mode 100644 index 0000000000..f3afd06eea --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.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.registry.client.event; + +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; + + +/** + * An event raised after a {@link ServiceInstance service instance} + * {@link ServiceDiscovery#unregister(ServiceInstance) unregistered} + * + * @see ServiceInstanceEvent + * @since 2.7.3 + */ +public class ServiceInstanceUnregisteredEvent extends ServiceInstanceEvent { + + public ServiceInstanceUnregisteredEvent(Object source, ServiceInstance serviceInstance) { + super(source, serviceInstance); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancesChangedEvent.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancesChangedEvent.java new file mode 100644 index 0000000000..d93173f585 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancesChangedEvent.java @@ -0,0 +1,64 @@ +/* + * 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.event; + +import org.apache.dubbo.event.Event; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; + +import java.util.Collection; + +import static java.util.Collections.unmodifiableCollection; + +/** + * An event raised after the {@link ServiceInstance instances} of one service has been changed. + * + * @see ServiceInstancesChangedListener + * @since 2.7.3 + */ +public class ServiceInstancesChangedEvent extends Event { + + private final String serviceName; + + private final Collection serviceInstances; + + /** + * @param serviceName The name of service that was changed + * @param serviceInstances all {@link ServiceInstance service instances} + * @throws IllegalArgumentException if source is null. + */ + public ServiceInstancesChangedEvent(String serviceName, Collection serviceInstances) { + super(serviceName); + this.serviceName = serviceName; + this.serviceInstances = unmodifiableCollection(serviceInstances); + } + + /** + * @return The name of service that was changed + */ + public String getServiceName() { + return serviceName; + } + + /** + * @return all {@link ServiceInstance service instances} + */ + public Collection getServiceInstances() { + return serviceInstances; + } + +} \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java new file mode 100644 index 0000000000..6bea21dae7 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/CustomizableServiceInstanceListener.java @@ -0,0 +1,54 @@ +/* + * 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.event.listener; + +import org.apache.dubbo.event.EventListener; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstanceCustomizer; +import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import static java.util.ServiceLoader.load; + + +/** + * An {@link EventListener event listener} to customize {@link ServiceInstance the service instance} by the instances of + * {@link ServiceInstanceCustomizer} {@link ServiceLoader SPI}. + * + * @see EventListener + * @see ServiceInstancePreRegisteredEvent + * @see ServiceInstanceCustomizer + * @since 2.7.3 + */ +public class CustomizableServiceInstanceListener implements EventListener { + + @Override + public void onEvent(ServiceInstancePreRegisteredEvent event) { + + ServiceLoader customizers = load(ServiceInstanceCustomizer.class); + + Iterator iterator = customizers.iterator(); + + while (iterator.hasNext()) { + ServiceInstanceCustomizer customizer = iterator.next(); + // customizes + customizer.customize(event.getServiceInstance()); + } + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java new file mode 100644 index 0000000000..6f68cb7dd3 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java @@ -0,0 +1,85 @@ +/* + * 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.event.listener; + +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.event.Event; +import org.apache.dubbo.event.GenericEventListener; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStartedEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStartingEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStoppedEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStoppingEvent; +import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstancePreUnregisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstanceRegisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstanceUnregisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; + +import static java.lang.String.format; + +/** + * A listener for logging the {@link Event Dubbo event} + * + * @since 2.7.3 + */ +public class LoggingEventListener extends GenericEventListener { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public void onEvent(ServiceDiscoveryStartingEvent event) { + info("%s is starting...", event.getServiceDiscovery()); + } + + public void onEvent(ServiceDiscoveryStartedEvent event) { + info("%s is started.", event.getServiceDiscovery()); + } + + public void onEvent(ServiceInstancePreRegisteredEvent event) { + info("%s is registering into %s...", event.getServiceInstance(), event.getSource()); + } + + public void onEvent(ServiceInstanceRegisteredEvent event) { + info("%s has been registered into %s.", event.getServiceInstance(), event.getSource()); + } + + public void onEvent(ServiceInstancesChangedEvent event) { + info("The services'[name : %s] instances[size : %s] has been changed.", event.getServiceName(), event.getServiceInstances().size()); + } + + public void onEvent(ServiceInstancePreUnregisteredEvent event) { + info("%s is registering from %s...", event.getServiceInstance(), event.getSource()); + } + + public void onEvent(ServiceInstanceUnregisteredEvent event) { + info("%s has been unregistered from %s.", event.getServiceInstance(), event.getSource()); + } + + public void onEvent(ServiceDiscoveryStoppingEvent event) { + info("%s is stopping...", event.getServiceDiscovery()); + } + + public void onEvent(ServiceDiscoveryStoppedEvent event) { + info("%s is stopped.", event.getServiceDiscovery()); + } + + private void info(String pattern, Object... args) { + if (logger.isInfoEnabled()) { + logger.info(format(pattern, args)); + } + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java new file mode 100644 index 0000000000..50bd73bd0a --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.registry.client.event.listener; + +import org.apache.dubbo.event.EventListener; +import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; + +/** + * The Service Discovery Changed {@link EventListener Event Listener} + * + * @see ServiceInstancesChangedEvent + * @since 2.7.3 + */ +public interface ServiceInstancesChangedListener extends EventListener { + + /** + * On {@link ServiceInstancesChangedEvent the service instances change event} + * + * @param event {@link ServiceInstancesChangedEvent} + */ + void onEvent(ServiceInstancesChangedEvent event); +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/DefaultMetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/DefaultMetadataServiceProxyFactory.java new file mode 100644 index 0000000000..65af122b09 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/DefaultMetadataServiceProxyFactory.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.registry.client.metadata; + +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.rpc.Protocol; + +import java.lang.reflect.Proxy; + +/** + * The factory of {@link MetadataService}'s {@link Proxy} + * + * @since 2.7.3 + */ +public class DefaultMetadataServiceProxyFactory implements MetadataServiceProxyFactory { + + private Protocol protocol; + + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } + + @Override + public MetadataService createProxy(ServiceInstance serviceInstance) { + return new MetadataServiceProxy(serviceInstance, protocol); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java new file mode 100644 index 0000000000..5c1cc8e9d1 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ExportedServicesRevisionMetadataCustomizer.java @@ -0,0 +1,69 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.compiler.support.ClassUtils; +import org.apache.dubbo.metadata.LocalMetadataService; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstanceMetadataCustomizer; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static java.lang.String.valueOf; +import static java.util.Objects.hash; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.EXPORTED_SERVICES_REVISION_KEY; + +/** + * The customizer to a add the metadata that the reversion of Dubbo exported services calculates. + *

    + * The reversion is calculated on the methods that all Dubbo exported interfaces declare + * + * @since 2.7.3 + */ +public class ExportedServicesRevisionMetadataCustomizer extends ServiceInstanceMetadataCustomizer { + + @Override + protected String buildMetadataKey(ServiceInstance serviceInstance) { + return EXPORTED_SERVICES_REVISION_KEY; + } + + @Override + protected String buildMetadataValue(ServiceInstance serviceInstance) { + LocalMetadataService localMetadataService = LocalMetadataService.getDefaultExtension(); + List exportedURLs = localMetadataService.getExportedURLs(); + Object[] data = exportedURLs.stream() + .map(URL::valueOf) // String to URL + .map(URL::getServiceInterface) // get the service interface + .filter(this::isNotMetadataService) // filter not MetadataService interface + .map(ClassUtils::forName) // load business interface class + .map(Class::getMethods) // get all public methods from business interface + .map(Arrays::asList) // Array to List + .flatMap(Collection::stream) // flat Stream to be Stream + .map(Object::toString) // Method to String + .sorted() // sort methods marking sure the calculation of reversion is stable + .toArray(); // Stream to Array + return valueOf(hash(data)); // calculate the hash code as reversion + } + + private boolean isNotMetadataService(String serviceInterface) { + return !MetadataService.class.getName().equals(serviceInterface); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxy.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxy.java new file mode 100644 index 0000000000..538365f6b9 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxy.java @@ -0,0 +1,114 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Protocol; +import org.apache.dubbo.rpc.proxy.InvokerInvocationHandler; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import static java.lang.reflect.Proxy.newProxyInstance; +import static org.apache.dubbo.registry.client.metadata.MetadataServiceURLBuilder.INSTANCE; + +/** + * The Proxy object for the {@link MetadataService} whose {@link ServiceInstance} providers may export multiple + * {@link Protocol protocols} at the same time. + * + * @see ServiceInstance + * @see MetadataService + * @since 2.7.3 + */ +class MetadataServiceProxy implements MetadataService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final List urls; + + private final Protocol protocol; + + public MetadataServiceProxy(ServiceInstance serviceInstance, Protocol protocol) { + this(INSTANCE.build(serviceInstance), protocol); + } + + public MetadataServiceProxy(List urls, Protocol protocol) { + this.urls = urls; + this.protocol = protocol; + } + + @Override + public String serviceName() { + return doInMetadataService(MetadataService::serviceName); + } + + @Override + public List getSubscribedURLs() { + return doInMetadataService(MetadataService::getSubscribedURLs); + } + + @Override + public List getExportedURLs(String serviceInterface, String group, String version, String protocol) { + return doInMetadataService(metadataService -> + metadataService.getExportedURLs(serviceInterface, group, version, protocol)); + } + + protected T doInMetadataService(Function callback) { + + T result = null; // execution result + + Throwable exception = null; // exception maybe present + + Iterator iterator = urls.iterator(); + + while (iterator.hasNext()) { // Executes MetadataService's method until success + URL url = iterator.next(); + Invoker invoker = null; + try { + invoker = this.protocol.refer(MetadataService.class, url); + MetadataService proxy = (MetadataService) newProxyInstance(getClass().getClassLoader(), + new Class[]{MetadataService.class}, new InvokerInvocationHandler(invoker)); + result = callback.apply(proxy); + exception = null; + } catch (Throwable e) { + exception = e; + // If met with some error, invoke next + if (logger.isErrorEnabled()) { + logger.error(e.getMessage(), e); + } + } finally { + if (invoker != null) { + // to destroy the Invoker finally + invoker.destroy(); + invoker = null; + } + } + } + + if (exception != null) { // If all executions were failed + throw new RuntimeException(exception.getMessage(), exception); + } + + return result; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxyFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxyFactory.java new file mode 100644 index 0000000000..8cb803c069 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceProxyFactory.java @@ -0,0 +1,51 @@ +/* + * 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; + +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; + +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; + +/** + * A factory to create a {@link MetadataService} proxy + * + * @see ServiceInstance + * @see MetadataService + * @since 2.7.3 + */ +@SPI("default") +public interface MetadataServiceProxyFactory { + + /** + * Create a {@link MetadataService} proxy via the specified {@link ServiceInstance} + * + * @param serviceInstance the instance of {@link ServiceInstance} + * @return non-null + */ + MetadataService createProxy(ServiceInstance serviceInstance); + + /** + * Get the default extension of {@link MetadataServiceProxyFactory} + * + * @return non-null + */ + static MetadataServiceProxyFactory getDefaultExtension() { + return getExtensionLoader(MetadataServiceProxyFactory.class).getDefaultExtension(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLBuilder.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLBuilder.java new file mode 100644 index 0000000000..fa6416b6c9 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLBuilder.java @@ -0,0 +1,81 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.URLBuilder; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static java.lang.String.valueOf; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceURLsParams; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getProviderHost; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getProviderPort; + +/** + * The {@link URL} builder for {@link MetadataService} + * + * @see MetadataService + * @since 2.7.3 + */ +class MetadataServiceURLBuilder { + + /** + * The singleton instance of {@link MetadataServiceURLBuilder} + */ + public static final MetadataServiceURLBuilder INSTANCE = new MetadataServiceURLBuilder(); + + private MetadataServiceURLBuilder() { + } + + /** + * Build the {@link URL urls} from {@link ServiceInstance#getMetadata() the metadata} of {@link ServiceInstance} + * + * @param serviceInstance {@link ServiceInstance} + * @return the not-null {@link List} + */ + public List build(ServiceInstance serviceInstance) { + + Map> paramsMap = getMetadataServiceURLsParams(serviceInstance); + + List urls = new ArrayList<>(paramsMap.size()); + + for (Map.Entry> entry : paramsMap.entrySet()) { + + URLBuilder urlBuilder = new URLBuilder(); + String protocol = entry.getKey(); + Map urlParams = entry.getValue(); + String host = getProviderHost(urlParams); + Integer port = getProviderPort(urlParams); + urlBuilder.setHost(host) + .setPort(port) + .setProtocol(protocol) + .setPath(MetadataService.class.getName()); + + // add parameters + entry.getValue().forEach((name, value) -> urlBuilder.addParameter(name, valueOf(value))); + + urls.add(urlBuilder.build()); + } + + return urls; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java new file mode 100644 index 0000000000..97ba53c340 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataServiceURLParamsMetadataCustomizer.java @@ -0,0 +1,60 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.metadata.LocalMetadataService; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstanceMetadataCustomizer; + +import java.util.List; + +import static org.apache.dubbo.metadata.MetadataService.toURLs; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URL_PARAMS_KEY; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceParameter; + +/** + * An {@link ServiceInstanceMetadataCustomizer} to customize the {@link URL urls} of {@link MetadataService} + * into {@link ServiceInstance#getMetadata() the service instances' metadata} + * + * @see ServiceInstanceMetadataCustomizer + * @since 2.7.3 + */ +public class MetadataServiceURLParamsMetadataCustomizer extends ServiceInstanceMetadataCustomizer { + + @Override + public String buildMetadataKey(ServiceInstance serviceInstance) { + return METADATA_SERVICE_URL_PARAMS_KEY; + } + + @Override + public String buildMetadataValue(ServiceInstance serviceInstance) { + + LocalMetadataService localMetadataService = LocalMetadataService.getDefaultExtension(); + + String serviceInterface = MetadataService.class.getName(); + + String group = serviceInstance.getServiceName(); + + String version = MetadataService.VERSION; + + List urls = localMetadataService.getExportedURLs(serviceInterface, group, version); + + return getMetadataServiceParameter(toURLs(urls)); + } +} 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 new file mode 100644 index 0000000000..85c3b65e82 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java @@ -0,0 +1,172 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.registry.client.ServiceInstance; + +import com.alibaba.fastjson.JSON; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.String.valueOf; +import static java.util.Collections.emptyMap; +import static org.apache.dubbo.common.utils.StringUtils.isBlank; +import static org.apache.dubbo.registry.integration.RegistryProtocol.DEFAULT_REGISTER_PROVIDER_KEYS; + +/** + * The Utilities class for the {@link ServiceInstance#getMetadata() metadata of the service instance} + * + * @see ServiceInstance#getMetadata() + * @see MetadataService + * @see URL + * @since 2.7.3 + */ +public class ServiceInstanceMetadataUtils { + + /** + * The key of metadata JSON of {@link MetadataService}'s {@link URL} + */ + public static String METADATA_SERVICE_URL_PARAMS_KEY = "dubbo.metadata-service.url-params"; + + /** + * The key of The revision for all exported Dubbo services. + */ + public static String EXPORTED_SERVICES_REVISION_KEY = "dubbo.exported-services.revision"; + + /** + * The {@link URL url's} parameter name of Dubbo Provider host + */ + public static final String HOST_PARAM_NAME = "provider.host"; + + /** + * The {@link URL url's} parameter name of Dubbo Provider port + */ + public static final String PORT_PARAM_NAME = "provider.port"; + + /** + * Get the multiple {@link URL urls'} parameters of {@link MetadataService MetadataService's} Metadata + * + * @param serviceInstance the instance of {@link ServiceInstance} + * @return non-null {@link Map}, the key is {@link URL#getProtocol() the protocol of URL}, the value is + * {@link #getMetadataServiceURLParams(ServiceInstance, String)} + */ + public static Map> getMetadataServiceURLsParams(ServiceInstance serviceInstance) { + Map metadata = serviceInstance.getMetadata(); + String param = metadata.get(METADATA_SERVICE_URL_PARAMS_KEY); + return isBlank(param) ? emptyMap() : (Map) JSON.parse(param); + } + + /** + * Get the {@link URL url's} parameters of {@link MetadataService MetadataService's} Metadata + * + * @param serviceInstance the instance of {@link ServiceInstance} + * @return non-null {@link Map} + */ + public static Map getMetadataServiceURLParams(ServiceInstance serviceInstance, String protocol) { + Map> params = getMetadataServiceURLsParams(serviceInstance); + return params.getOrDefault(protocol, emptyMap()); + } + + /** + * The provider port from {@link ServiceInstance the specified service instance} + * + * @param serviceInstance {@link ServiceInstance the specified service instance} + * @param protocol the protocol name + * @return The protocol port if found, or null + */ + public static Integer getProviderPort(ServiceInstance serviceInstance, String protocol) { + Map params = getMetadataServiceURLParams(serviceInstance, protocol); + return getProviderPort(params); + } + + public static String getProviderHost(ServiceInstance serviceInstance, String protocol) { + Map params = getMetadataServiceURLParams(serviceInstance, protocol); + return getProviderHost(params); + } + + public static String getMetadataServiceParameter(List urls) { + Map> params = new HashMap<>(); + + urls.forEach(url -> { + String protocol = url.getProtocol(); + params.put(protocol, getParams(url)); + }); + + if (params.isEmpty()) { + return null; + } + + return JSON.toJSONString(params); + } + + private static Map getParams(URL providerURL) { + Map params = new LinkedHashMap<>(); + setDefaultParams(params, providerURL); + // set provider host + setProviderHostParam(params, providerURL); + // set provider port + setProviderPortParam(params, providerURL); + return params; + } + + public static String getProviderHost(Map params) { + return valueOf(params.get(HOST_PARAM_NAME)); + } + + public static Integer getProviderPort(Map params) { + return Integer.valueOf(valueOf(params.get(PORT_PARAM_NAME))); + } + + /** + * The revision for all exported Dubbo services from the specified {@link ServiceInstance}. + * + * @param serviceInstance the specified {@link ServiceInstance} + * @return null if not exits + */ + public static String getExportedServicesRevision(ServiceInstance serviceInstance) { + Map metadata = serviceInstance.getMetadata(); + return metadata.get(EXPORTED_SERVICES_REVISION_KEY); + } + + private static void setProviderHostParam(Map params, URL providerURL) { + params.put(HOST_PARAM_NAME, providerURL.getHost()); + } + + private static void setProviderPortParam(Map params, URL providerURL) { + params.put(PORT_PARAM_NAME, valueOf(providerURL.getPort())); + } + + /** + * Set the default parameters via the specified {@link URL providerURL} + * + * @param params the parameters + * @param providerURL the provider's {@link URL} + */ + private static void setDefaultParams(Map params, URL providerURL) { + for (String parameterName : DEFAULT_REGISTER_PROVIDER_KEYS) { + String parameterValue = providerURL.getParameter(parameterName); + if (!isBlank(parameterValue)) { + params.put(parameterName, parameterValue); + } + } + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/package-info.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/package-info.java new file mode 100644 index 0000000000..7057c3b64a --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +/** + * * The inspiration of service registration and discovery comes from + * Spring Cloud Commons. + * + * @since 2.7.3 + */ +package org.apache.dubbo.registry.client; \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/RandomServiceInstanceSelector.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/RandomServiceInstanceSelector.java new file mode 100644 index 0000000000..1b862da2b6 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/RandomServiceInstanceSelector.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.registry.client.selector; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.registry.client.ServiceInstance; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * The {@link ServiceInstanceSelector} implementation based on Random algorithm + * + * @see ThreadLocalRandom + * @see ServiceInstanceSelector + * @since 2.7.3 + */ +public class RandomServiceInstanceSelector implements ServiceInstanceSelector { + + @Override + public ServiceInstance select(URL registryURL, List serviceInstances) { + int size = serviceInstances.size(); + if (size < 1) { + return null; + } + int index = selectIndexRandomly(size); + return serviceInstances.get(index); + } + + protected int selectIndexRandomly(int size) { + return ThreadLocalRandom.current().nextInt(size); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/ServiceInstanceSelector.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/ServiceInstanceSelector.java new file mode 100644 index 0000000000..195e8b318d --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/selector/ServiceInstanceSelector.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.registry.client.selector; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.registry.client.ServiceInstance; + +import java.util.List; + +/** + * The {@link ServiceInstance} Selector + * + * @since 2.7.3 + */ +@SPI("random") +public interface ServiceInstanceSelector { + + /** + * Select an instance of {@link ServiceInstance} by the specified {@link ServiceInstance service instances} + * + * @param registryURL The {@link URL url} of registry + * @param serviceInstances the specified {@link ServiceInstance service instances} + * @return an instance of {@link ServiceInstance} if available, or null + */ + @Adaptive("service-instance-selector") + ServiceInstance select(URL registryURL, List serviceInstances); +} 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 f74931b329..2f3a83567a 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 @@ -58,22 +58,19 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DISABLED_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY; 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.PROTOCOL_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; -import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.APP_DYNAMIC_CONFIGURATORS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.COMPATIBLE_CONFIG_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY; -import static org.apache.dubbo.registry.Constants.CONFIGURATORS_SUFFIX; import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.DYNAMIC_CONFIGURATORS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL; @@ -81,6 +78,9 @@ import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATE import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.ROUTERS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_PROTOCOL; +import static org.apache.dubbo.registry.Constants.CONFIGURATORS_SUFFIX; +import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY; /** 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 b9ad1b3ce5..9afa7ab9fd 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 @@ -52,58 +52,58 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import static java.util.concurrent.Executors.newSingleThreadExecutor; -import static org.apache.dubbo.rpc.cluster.Constants.LOADBALANCE_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.WARMUP_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.WEIGHT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; import static org.apache.dubbo.common.constants.CommonConstants.APPLICATION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; import static org.apache.dubbo.common.constants.CommonConstants.HIDE_KEY_PREFIX; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.RELEASE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.common.constants.FilterConstants.VALIDATION_KEY; import static org.apache.dubbo.common.constants.QosConstants.ACCEPT_FOREIGN_IP; -import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; import static org.apache.dubbo.common.constants.QosConstants.QOS_ENABLE; import static org.apache.dubbo.common.constants.QosConstants.QOS_PORT; -import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; -import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; -import static org.apache.dubbo.common.constants.FilterConstants.VALIDATION_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.MONITOR_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY; -import static org.apache.dubbo.registry.Constants.CONFIGURATORS_SUFFIX; import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY; -import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL; -import static org.apache.dubbo.registry.Constants.DEFAULT_REGISTRY; -import static org.apache.dubbo.registry.Constants.EXTRA_KEYS_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.OVERRIDE_PROTOCOL; import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY; -import static org.apache.dubbo.registry.Constants.PROVIDER_PROTOCOL; -import static org.apache.dubbo.registry.Constants.REGISTER_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_PROTOCOL; import static org.apache.dubbo.common.constants.RegistryConstants.ROUTERS_CATEGORY; +import static org.apache.dubbo.common.utils.UrlUtils.classifyUrls; +import static org.apache.dubbo.registry.Constants.CONFIGURATORS_SUFFIX; +import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL; +import static org.apache.dubbo.registry.Constants.DEFAULT_REGISTRY; +import static org.apache.dubbo.registry.Constants.EXTRA_KEYS_KEY; +import static org.apache.dubbo.registry.Constants.PROVIDER_PROTOCOL; +import static org.apache.dubbo.registry.Constants.REGISTER_IP_KEY; +import static org.apache.dubbo.registry.Constants.REGISTER_KEY; import static org.apache.dubbo.registry.Constants.SIMPLIFIED_KEY; import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY; import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY; import static org.apache.dubbo.remoting.Constants.CHECK_KEY; import static org.apache.dubbo.remoting.Constants.CODEC_KEY; -import static org.apache.dubbo.remoting.Constants.EXCHANGER_KEY; -import static org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY; import static org.apache.dubbo.remoting.Constants.CONNECTIONS_KEY; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; +import static org.apache.dubbo.remoting.Constants.EXCHANGER_KEY; +import static org.apache.dubbo.remoting.Constants.SERIALIZATION_KEY; import static org.apache.dubbo.rpc.Constants.DEPRECATED_KEY; import static org.apache.dubbo.rpc.Constants.INTERFACES; import static org.apache.dubbo.rpc.Constants.MOCK_KEY; import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; -import static org.apache.dubbo.common.utils.UrlUtils.classifyUrls; +import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.LOADBALANCE_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.WARMUP_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.WEIGHT_KEY; /** * RegistryProtocol diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java index 06915098da..2b2dab7a7b 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/AbstractRegistryFactory.java @@ -99,8 +99,13 @@ public abstract class AbstractRegistryFactory implements RegistryFactory { if (registry != null) { return registry; } - //create registry by spi/ioc - registry = createRegistry(url); + // creates an instance of ServiceOrientedRegistry if supported + // since 2.7.3 + registry = ServiceOrientedRegistry.create(url); + if (registry == null) { // If not supported, create the default Registry + //create registry by spi/ioc + registry = createRegistry(url); + } if (registry == null) { throw new IllegalStateException("Can not create registry " + url); } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/ServiceOrientedRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/ServiceOrientedRegistry.java new file mode 100644 index 0000000000..945df0d680 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/ServiceOrientedRegistry.java @@ -0,0 +1,472 @@ +/* + * 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.support; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.metadata.LocalMetadataService; +import org.apache.dubbo.metadata.MetadataService; +import org.apache.dubbo.metadata.ServiceNameMapping; +import org.apache.dubbo.registry.NotifyListener; +import org.apache.dubbo.registry.Registry; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceDiscoveryFactory; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.metadata.MetadataServiceProxyFactory; +import org.apache.dubbo.registry.client.selector.ServiceInstanceSelector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.of; +import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_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.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE; +import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY; +import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; +import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty; +import static org.apache.dubbo.common.utils.CollectionUtils.isNotEmpty; +import static org.apache.dubbo.common.utils.StringUtils.isBlank; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getExportedServicesRevision; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceURLsParams; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getProviderHost; +import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getProviderPort; + +/** + * Service-Oriented {@link Registry} that is dislike the traditional {@link Registry} will not communicate to + * registry immediately instead of persisting into the metadata's repository when the Dubbo service exports. + * The metadata repository will be used as the data source of Dubbo Metadata service that is about to export and be + * subscribed by the consumers. + *

    + * + * @see ServiceDiscovery + * @see FailbackRegistry + * @since 2.7.3 + */ +public class ServiceOrientedRegistry extends FailbackRegistry { + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final ServiceDiscovery serviceDiscovery; + + private final Set subscribedServices; + + private final ServiceNameMapping serviceNameMapping; + + private final LocalMetadataService localMetadataService; + + private final MetadataServiceProxyFactory metadataServiceProxyFactory; + + + public ServiceOrientedRegistry(URL registryURL) { + super(registryURL); + this.serviceDiscovery = buildServiceDiscovery(registryURL); + this.subscribedServices = buildSubscribedServices(registryURL); + this.serviceNameMapping = ServiceNameMapping.getDefaultExtension(); + this.localMetadataService = LocalMetadataService.getDefaultExtension(); + this.metadataServiceProxyFactory = MetadataServiceProxyFactory.getDefaultExtension(); + } + + private Set buildSubscribedServices(URL url) { + String subscribedServiceNames = url.getParameter(SUBSCRIBED_SERVICE_NAMES_KEY); + return isBlank(subscribedServiceNames) ? emptySet() : + unmodifiableSet(of(subscribedServiceNames.split(",")) + .map(String::trim) + .filter(StringUtils::isNotEmpty) + .collect(toSet())); + } + + private ServiceDiscovery buildServiceDiscovery(URL url) { + ServiceDiscoveryFactory serviceDiscoveryFactory = ServiceDiscoveryFactory.getDefaultExtension(); + ServiceDiscovery serviceDiscovery = serviceDiscoveryFactory.create(url); + serviceDiscovery.start(); + return serviceDiscovery; + } + + protected boolean shouldRegister(URL providerURL) { + String side = providerURL.getParameter(SIDE_KEY); + + boolean should = PROVIDER_SIDE.equals(side); // Only register the Provider. + + if (!should) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("The URL[%s] should not be registered.", providerURL.toString())); + } + } + + return should; + } + + protected boolean shouldSubscribe(URL subscribedURL) { + return !shouldRegister(subscribedURL); + } + + @Override + public void doRegister(URL url) { + if (!shouldRegister(url)) { // Should Not Register + return; + } + if (localMetadataService.exportURL(url)) { + if (logger.isInfoEnabled()) { + logger.info(format("The URL[%s] registered successfully.", url.toString())); + } + } else { + if (logger.isWarnEnabled()) { + logger.info(format("The URL[%s] has been registered.", url.toString())); + } + } + } + + @Override + public void doUnregister(URL url) { + if (!shouldRegister(url)) { + return; + } + if (localMetadataService.unexportURL(url)) { + if (logger.isInfoEnabled()) { + logger.info(format("The URL[%s] deregistered successfully.", url.toString())); + } + } else { + if (logger.isWarnEnabled()) { + logger.info(format("The URL[%s] has been deregistered.", url.toString())); + } + } + } + + @Override + public void doSubscribe(URL url, NotifyListener listener) { + if (!shouldSubscribe(url)) { // Should Not Subscribe + return; + } + subscribeURLs(url, listener); + } + + @Override + public void doUnsubscribe(URL url, NotifyListener listener) { + localMetadataService.unsubscribeURL(url); + } + + @Override + public boolean isAvailable() { + return !serviceDiscovery.getServices().isEmpty(); + } + + @Override + public void destroy() { + super.destroy(); + // stop ServiceDiscovery + serviceDiscovery.stop(); + } + + protected void subscribeURLs(URL url, NotifyListener listener) { + + localMetadataService.subscribeURL(url); + + Set serviceNames = getServices(url); + + serviceNames.forEach(serviceName -> subscribeURLs(url, listener, serviceName)); + + } + + protected void subscribeURLs(URL url, NotifyListener listener, String serviceName) { + + List serviceInstances = serviceDiscovery.getInstances(serviceName); + + subscribeURLs(url, listener, serviceName, serviceInstances); + + // Add Listener + serviceDiscovery.addServiceInstancesChangedListener(serviceName, event -> { + subscribeURLs(url, listener, event.getServiceName(), new ArrayList<>(event.getServiceInstances())); + }); + } + + protected void subscribeURLs(URL subscribedURL, NotifyListener listener, String serviceName, + Collection serviceInstances) { + + if (isEmpty(serviceInstances)) { + logger.warn(format("There is no instance in service[name : %s]", serviceName)); + return; + } + + List subscribedURLs = getSubscribedURLs(subscribedURL, serviceInstances); + + listener.notify(subscribedURLs); + } + + private List getSubscribedURLs(URL subscribedURL, Collection instances) { + + List subscribedURLs = new LinkedList<>(); + + // local service instances could be mutable + List serviceInstances = instances.stream() + .filter(ServiceInstance::isEnabled) + .filter(ServiceInstance::isHealthy) + .collect(Collectors.toList()); + + /** + * A caches all revisions of exported services in different {@link ServiceInstance}s + * associating with the {@link URL urls} + */ + Map> revisionURLsCache = new HashMap<>(); + + // try to get the exported URLs from every instance until it's successful. + for (int i = 0; i < serviceInstances.size(); i++) { + // select a instance of {@link ServiceInstance} + ServiceInstance selectedInstance = selectServiceInstance(serviceInstances); + List templateURLs = getTemplateURLs(subscribedURL, selectedInstance, revisionURLsCache); + if (isNotEmpty(templateURLs)) { + // add templateURLs into subscribedURLs + subscribedURLs.addAll(templateURLs); + // remove the selected ServiceInstance in this time, it remains N - 1 elements. + serviceInstances.remove(selectedInstance); + break; + } + } + + // Clone the subscribed URLs from the template URLs + List clonedURLs = cloneSubscribedURLs(subscribedURL, serviceInstances, revisionURLsCache); + // Add all cloned URLs into subscribedURLs + subscribedURLs.addAll(clonedURLs); + // clear all revisions + revisionURLsCache.clear(); + // clear local service instances + serviceInstances.clear(); + + return subscribedURLs; + } + + private List cloneSubscribedURLs(URL subscribedURL, Collection serviceInstances, + Map> revisionURLsCache) { + + // If revisionURLsCache is not empty, clone the template URLs to be the subscribed URLs + if (!revisionURLsCache.isEmpty()) { + + List clonedURLs = new LinkedList<>(); + + Iterator iterator = serviceInstances.iterator(); + + while (iterator.hasNext()) { + + ServiceInstance serviceInstance = iterator.next(); + + List templateURLs = getTemplateURLs(subscribedURL, serviceInstance, revisionURLsCache); + // The parameters of URLs that the MetadataService exported + Map> serviceURLsParams = getMetadataServiceURLsParams(serviceInstance); + + templateURLs.forEach(templateURL -> { + + String protocol = templateURL.getProtocol(); + + Map serviceURLParams = serviceURLsParams.get(protocol); + + String host = getProviderHost(serviceURLParams); + + Integer port = getProviderPort(serviceURLParams); + + /** + * clone the subscribed {@link URL urls} based on the template {@link URL url} + */ + URL newSubscribedURL = new URL(protocol, host, port, templateURL.getParameters()); + clonedURLs.add(newSubscribedURL); + }); + } + + return clonedURLs; + } + + return Collections.emptyList(); + } + + + /** + * Select one {@link ServiceInstance} from the {@link List list} + * + * @param serviceInstances the {@link List list} of {@link ServiceInstance} + * @return null if serviceInstances is empty. + */ + private ServiceInstance selectServiceInstance(List serviceInstances) { + ServiceInstanceSelector selector = getExtensionLoader(ServiceInstanceSelector.class).getAdaptiveExtension(); + return selector.select(getUrl(), serviceInstances); + } + + + /** + * Get the template {@link URL urls} from the specified {@link ServiceInstance}. + *

    + * Typically, the revisions of all {@link ServiceInstance instances} in one service are same. However, + * if one service is upgrading one or more Dubbo service interfaces, one of them may have the multiple declarations + * is deploying in the different {@link ServiceInstance service instances}, thus, it has to compare the interface + * contract one by one, the "revision" that is the number is introduced to identify all Dubbo exported interfaces in + * one {@link ServiceInstance service instance}. + *

    + * First, put the revision {@link ServiceInstance service instance} + * associating {@link #getProviderExportedURLs(URL, ServiceInstance) exported URLs} into cache. + *

    + * And then compare a new {@link ServiceInstance service instances'} revision with cached one,If they are equal, + * return the cached template {@link URL urls} immediately, or to get template {@link URL urls} that the provider + * {@link ServiceInstance instance} exported via executing {@link #getProviderExportedURLs(URL, ServiceInstance)} + * method. + *

    + * Eventually, the retrieving result will be cached and returned. + * + * @param subscribedURL the subscribed {@link URL url} + * @param selectedInstance the {@link ServiceInstance} + * @param revisionURLsCache A caches all revisions of exported services in different {@link ServiceInstance}s + * associating with the {@link URL urls} + * @return non-null {@link List} of {@link URL urls} + */ + protected List getTemplateURLs(URL subscribedURL, ServiceInstance selectedInstance, + Map> revisionURLsCache) { + // get the revision from the specified {@link ServiceInstance} + String revision = getExportedServicesRevision(selectedInstance); + // try to get templateURLs from cache + List templateURLs = revisionURLsCache.get(revision); + + if (isEmpty(templateURLs)) { // not exists or getting failed last time + + if (!revisionURLsCache.isEmpty()) { // it's not first time + if (logger.isWarnEnabled()) { + logger.warn(format("The ServiceInstance[id: %s, host : %s , port : %s] has different revision : %s" + + ", please make sure the service [name : %s] is changing or not.", + selectedInstance.getId(), + selectedInstance.getHost(), + selectedInstance.getPort(), + revision, + selectedInstance.getServiceName() + )); + } + } + // get or get again + templateURLs = getProviderExportedURLs(subscribedURL, selectedInstance); + // put into cache + revisionURLsCache.put(revision, templateURLs); + } + + return templateURLs; + } + + /** + * Get the exported {@link URL urls} from the specified provider {@link ServiceInstance instance} + * + * @param subscribedURL the subscribed {@link URL url} + * @param providerInstance the target provider {@link ServiceInstance instance} + * @return non-null {@link List} of {@link URL urls} + */ + protected List getProviderExportedURLs(URL subscribedURL, ServiceInstance providerInstance) { + + List exportedURLs = emptyList(); + + String serviceInterface = subscribedURL.getServiceInterface(); + String group = subscribedURL.getParameter(GROUP_KEY); + String version = subscribedURL.getParameter(VERSION_KEY); + // The subscribed protocol may be null + String protocol = subscribedURL.getParameter(PROTOCOL_KEY); + + try { + MetadataService metadataService = metadataServiceProxyFactory.createProxy(providerInstance); + List urls = metadataService.getExportedURLs(serviceInterface, group, version, protocol); + exportedURLs = urls.stream().map(URL::valueOf).collect(Collectors.toList()); + } catch (Throwable e) { + if (logger.isErrorEnabled()) { + logger.error(e.getMessage(), e); + } + } + + return exportedURLs; + } + + + protected Set getServices(URL subscribedURL) { + Set serviceNames = getSubscribedServices(); + if (isEmpty(serviceNames)) { + serviceNames = findMappedServices(subscribedURL); + } + return serviceNames; + } + + /** + * Get the subscribed service names + * + * @return non-null + */ + public Set getSubscribedServices() { + return subscribedServices; + } + + /** + * Get the mapped services name by the specified {@link URL} + * + * @param subscribedURL + * @return + */ + protected Set findMappedServices(URL subscribedURL) { + String serviceInterface = subscribedURL.getServiceInterface(); + String group = subscribedURL.getParameter(GROUP_KEY); + String version = subscribedURL.getParameter(VERSION_KEY); + String protocol = subscribedURL.getParameter(PROTOCOL_KEY, DUBBO_PROTOCOL); + return serviceNameMapping.get(serviceInterface, group, version, protocol); + } + + /** + * Create an instance of {@link ServiceOrientedRegistry} if supported + * + * @param registryURL the {@link URL url} of registry + * @return null if not supported + */ + public static ServiceOrientedRegistry create(URL registryURL) { + return supports(registryURL) ? new ServiceOrientedRegistry(registryURL) : null; + } + + /** + * Supports or not ? + * + * @param registryURL the {@link URL url} of registry + * @return if supported, return true, or false + */ + public static boolean supports(URL registryURL) { + return SERVICE_REGISTRY_TYPE.equalsIgnoreCase(registryURL.getParameter(REGISTRY_TYPE_KEY)); + } + + /** + * Get the instance of {@link ServiceDiscovery} + * + * @return non-null + */ + public ServiceDiscovery getServiceDiscovery() { + return serviceDiscovery; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..20b8e66739 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +event-publishing=org.apache.dubbo.registry.client.EventPublishingServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.MetadataServiceProxyFactory b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.MetadataServiceProxyFactory new file mode 100644 index 0000000000..4886058702 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.metadata.MetadataServiceProxyFactory @@ -0,0 +1 @@ +default=org.apache.dubbo.registry.client.metadata.DefaultMetadataServiceProxyFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.selector.ServiceInstanceSelector b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.selector.ServiceInstanceSelector new file mode 100644 index 0000000000..d3cca8cdf6 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.selector.ServiceInstanceSelector @@ -0,0 +1 @@ +random=org.apache.dubbo.registry.client.selector.RandomServiceInstanceSelector \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener new file mode 100644 index 0000000000..4bb27fbb76 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.event.EventListener @@ -0,0 +1,2 @@ +org.apache.dubbo.registry.client.event.listener.CustomizableServiceInstanceListener +org.apache.dubbo.registry.client.event.listener.LoggingEventListener \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer new file mode 100644 index 0000000000..1d5b9e1281 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceInstanceCustomizer @@ -0,0 +1,2 @@ +org.apache.dubbo.registry.client.metadata.MetadataServiceURLParamsMetadataCustomizer +org.apache.dubbo.registry.client.metadata.ExportedServicesRevisionMetadataCustomizer \ No newline at end of file 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 new file mode 100644 index 0000000000..71cc0af447 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/DefaultServiceInstanceTest.java @@ -0,0 +1,60 @@ +/* + * 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; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +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 DefaultServiceInstance} Test + * + * @since 2.7.3 + */ +public class DefaultServiceInstanceTest { + + public static DefaultServiceInstance INSTANCE = + new DefaultServiceInstance("A", "127.0.0.1", 8080); + + @BeforeEach + public void init() { + INSTANCE = new DefaultServiceInstance("A", "127.0.0.1", 8080); + } + + @Test + public void testDefaultValues() { + assertTrue(INSTANCE.isEnabled()); + assertTrue(INSTANCE.isHealthy()); + assertTrue(INSTANCE.getMetadata().isEmpty()); + } + + @Test + public void testSetAndGetValues() { + INSTANCE.setEnabled(false); + INSTANCE.setHealthy(false); + + assertEquals("A", INSTANCE.getServiceName()); + assertEquals("127.0.0.1", INSTANCE.getHost()); + assertEquals(8080, INSTANCE.getPort()); + assertFalse(INSTANCE.isEnabled()); + assertFalse(INSTANCE.isHealthy()); + assertTrue(INSTANCE.getMetadata().isEmpty()); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryTest.java new file mode 100644 index 0000000000..18a8d6188f --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscoveryTest.java @@ -0,0 +1,164 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.event.EventListener; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStartedEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStartingEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStoppedEvent; +import org.apache.dubbo.registry.client.event.ServiceDiscoveryStoppingEvent; +import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; +import org.apache.dubbo.registry.client.event.ServiceInstanceRegisteredEvent; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +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; + +/** + * {@link EventPublishingServiceDiscovery} Test + * + * @since 2.7.3 + */ +public class EventPublishingServiceDiscoveryTest { + + private static final URL url = URL.valueOf("zookeeper://127.0.0.1:2181/"); + + private EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); + + private InMemoryServiceDiscovery delegate; + + private EventPublishingServiceDiscovery serviceDiscovery; + + private ServiceDiscoveryTest serviceDiscoveryTest; + + @BeforeEach + public void init() { + + // remove all EventListeners + eventDispatcher.removeAllEventListeners(); + + delegate = new InMemoryServiceDiscovery(); + + serviceDiscovery = new EventPublishingServiceDiscovery(delegate); + + serviceDiscoveryTest = new ServiceDiscoveryTest(); + + serviceDiscoveryTest.setServiceDiscovery(serviceDiscovery); + + // ServiceDiscoveryStartingEvent + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceDiscoveryStartingEvent event) { + assertEquals(delegate, event.getServiceDiscovery()); + } + }); + + // ServiceDiscoveryStartedEvent + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceDiscoveryStartedEvent event) { + assertEquals(delegate, event.getServiceDiscovery()); + } + }); + + // ServiceInstancePreRegisteredEvent + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceInstancePreRegisteredEvent event) { + assertNotNull(event.getServiceInstance()); + } + }); + + // ServiceInstanceRegisteredEvent + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceInstanceRegisteredEvent event) { + assertNotNull(event.getServiceInstance()); + } + }); + + assertFalse(serviceDiscovery.isStarted()); + assertFalse(serviceDiscovery.isStopped()); + + // test start() + serviceDiscoveryTest.init(); + + assertTrue(serviceDiscovery.isStarted()); + assertFalse(serviceDiscovery.isStopped()); + } + + @AfterEach + public void destroy() { + + // ServiceDiscoveryStoppingEvent + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceDiscoveryStoppingEvent event) { + assertEquals(delegate, event.getServiceDiscovery()); + } + }); + + // ServiceDiscoveryStoppedEvent + eventDispatcher.addEventListener(new EventListener() { + @Override + public void onEvent(ServiceDiscoveryStoppedEvent event) { + assertEquals(delegate, event.getServiceDiscovery()); + } + }); + + assertTrue(serviceDiscovery.isStarted()); + assertFalse(serviceDiscovery.isStopped()); + + // test stop() + serviceDiscoveryTest.destroy(); + + assertTrue(serviceDiscovery.isStarted()); + assertTrue(serviceDiscovery.isStopped()); + } + + @Test + public void testToString() { + serviceDiscoveryTest.testToString(); + } + + @Test + public void testRegisterAndUpdateAndUnregister() { + serviceDiscoveryTest.testRegisterAndUpdateAndUnregister(); + } + + @Test + public void testGetServices() { + serviceDiscoveryTest.testGetServices(); + } + + @Test + public void testGetInstances() { + serviceDiscoveryTest.testGetInstances(); + } + + @Test + public void testGetInstancesWithHealthy() { + serviceDiscoveryTest.testGetInstancesWithHealthy(); + } +} \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java new file mode 100644 index 0000000000..3556cd2388 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java @@ -0,0 +1,110 @@ +/* + * 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; + +import org.apache.dubbo.common.utils.DefaultPage; +import org.apache.dubbo.common.utils.Page; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.emptyList; + +/** + * In-Memory {@link ServiceDiscovery} implementation + * + * @since 2.7.3 + */ +public class InMemoryServiceDiscovery implements ServiceDiscovery { + + private final EventDispatcher dispatcher = EventDispatcher.getDefaultExtension(); + + private Map> repository = new HashMap<>(); + + @Override + public Set getServices() { + return repository.keySet(); + } + + @Override + public Page getInstances(String serviceName, int offset, int pageSize, boolean healthyOnly) { + List instances = new ArrayList<>(repository.computeIfAbsent(serviceName, s -> new LinkedList<>())); + int totalSize = instances.size(); + List data = emptyList(); + if (offset < totalSize) { + int toIndex = offset + pageSize > totalSize - 1 ? totalSize : offset + pageSize; + data = instances.subList(offset, toIndex); + } + if (healthyOnly) { + Iterator iterator = data.iterator(); + while (iterator.hasNext()) { + ServiceInstance instance = iterator.next(); + if (!instance.isHealthy()) { + iterator.remove(); + } + } + } + return new DefaultPage<>(offset, pageSize, data, totalSize); + } + + public String toString() { + return "InMemoryServiceDiscovery"; + } + + @Override + public void register(ServiceInstance serviceInstance) throws RuntimeException { + String serviceName = serviceInstance.getServiceName(); + List serviceInstances = repository.computeIfAbsent(serviceName, s -> new LinkedList<>()); + if (!serviceInstances.contains(serviceInstance)) { + serviceInstances.add(serviceInstance); + } + } + + @Override + public void update(ServiceInstance serviceInstance) throws RuntimeException { + unregister(serviceInstance); + register(serviceInstance); + } + + @Override + public void unregister(ServiceInstance serviceInstance) throws RuntimeException { + String serviceName = serviceInstance.getServiceName(); + List serviceInstances = repository.computeIfAbsent(serviceName, s -> new LinkedList<>()); + serviceInstances.remove(serviceInstance); + } + + @Override + public void start() { + + } + + @Override + public void stop() { + } + + @Override + public void addServiceInstancesChangedListener(String serviceName, ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException { + dispatcher.addEventListener(listener); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscoveryFactory.java new file mode 100644 index 0000000000..4a4104d38f --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscoveryFactory.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.registry.client; + +import org.apache.dubbo.common.URL; + +/** + * {@link InMemoryServiceDiscovery} Factory + * + * @see InMemoryServiceDiscovery + * @since 2.7.3 + */ +public class InMemoryServiceDiscoveryFactory implements ServiceDiscoveryFactory { + + @Override + public ServiceDiscovery create(URL connectionURL) { + return new InMemoryServiceDiscovery(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactoryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactoryTest.java new file mode 100644 index 0000000000..bf9f1db130 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryFactoryTest.java @@ -0,0 +1,55 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.apache.dubbo.common.URL.valueOf; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link ServiceDiscoveryFactory} Test + * + * @since 2.7.3 + */ +public class ServiceDiscoveryFactoryTest { + + private static final URL dubboURL = valueOf("dubbo://localhost:20880"); + + private static final URL inMemoryURL = valueOf("in-memory://localhost:12345"); + + private ServiceDiscoveryFactory serviceDiscoveryFactory; + + @BeforeEach + public void init() { + serviceDiscoveryFactory = ServiceDiscoveryFactory.getDefaultExtension(); + } + + @Test + public void testClass() { + assertEquals(EventPublishingServiceDiscoveryFactory.class, serviceDiscoveryFactory.getClass()); + } + + @Test + public void testCreate() { + ServiceDiscovery serviceDiscovery = serviceDiscoveryFactory.create(inMemoryURL); + assertEquals(EventPublishingServiceDiscovery.class, serviceDiscovery.getClass()); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryTest.java new file mode 100644 index 0000000000..ff38f2eedd --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/ServiceDiscoveryTest.java @@ -0,0 +1,264 @@ +/* + * 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; + +import org.apache.dubbo.common.utils.Page; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +import static java.util.Arrays.asList; +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 ServiceDiscovery} Test case + * + * @since 2.7.3 + */ +public class ServiceDiscoveryTest { + + private ServiceDiscovery serviceDiscovery; + + @BeforeEach + public void init() { + if (serviceDiscovery == null) { + setServiceDiscovery(new InMemoryServiceDiscovery()); + } + // test start() + serviceDiscovery.start(); + } + + @AfterEach + public void destroy() { + // test stop() + serviceDiscovery.stop(); + } + + @Test + public void testToString() { + assertEquals("InMemoryServiceDiscovery", serviceDiscovery.toString()); + } + + @Test + public void testRegisterAndUpdateAndUnregister() { + + // register + DefaultServiceInstance serviceInstance = new DefaultServiceInstance("A", "127.0.0.1", 8080); + serviceDiscovery.register(serviceInstance); + + List serviceInstances = serviceDiscovery.getInstances("A"); + + assertEquals(1, serviceInstances.size()); + assertEquals(serviceInstances.get(0), serviceInstance); + + serviceInstance.setEnabled(false); + serviceInstance.setHealthy(false); + serviceInstance.setPort(9090); + + // update + serviceDiscovery.update(serviceInstance); + + serviceInstances = serviceDiscovery.getInstances("A"); + + assertEquals(1, serviceInstances.size()); + assertEquals(serviceInstances.get(0), serviceInstance); + + // unregister + serviceDiscovery.unregister(serviceInstance); + + serviceInstances = serviceDiscovery.getInstances("A"); + assertTrue(serviceInstances.isEmpty()); + } + + + @Test + public void testGetServices() { + serviceDiscovery.register(new DefaultServiceInstance("A", "127.0.0.1", 8080)); + serviceDiscovery.register(new DefaultServiceInstance("B", "127.0.0.1", 8080)); + serviceDiscovery.register(new DefaultServiceInstance("C", "127.0.0.1", 8080)); + assertEquals(new HashSet<>(asList("A", "B", "C")), serviceDiscovery.getServices()); + } + + @Test + public void testGetInstances() { + + List instances = asList( + new DefaultServiceInstance("A", "127.0.0.1", 8080), + new DefaultServiceInstance("A", "127.0.0.1", 8081), + new DefaultServiceInstance("A", "127.0.0.1", 8082) + ); + + instances.forEach(serviceDiscovery::register); + + // Duplicated + serviceDiscovery.register(new DefaultServiceInstance("A", "127.0.0.1", 8080)); + // Duplicated + serviceDiscovery.register(new DefaultServiceInstance("A", "127.0.0.1", 8081)); + + // offset starts 0 + int offset = 0; + // pageSize > total elements + int pageSize = 5; + + Page page = serviceDiscovery.getInstances("A", offset, pageSize); + assertEquals(0, page.getOffset()); + assertEquals(5, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(3, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + // pageSize < total elements + pageSize = 2; + + page = serviceDiscovery.getInstances("A", offset, pageSize); + assertEquals(0, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(2, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + offset = 1; + page = serviceDiscovery.getInstances("A", offset, pageSize); + assertEquals(1, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(2, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + offset = 2; + page = serviceDiscovery.getInstances("A", offset, pageSize); + assertEquals(2, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(1, page.getData().size()); + assertTrue(page.hasData()); + + offset = 3; + page = serviceDiscovery.getInstances("A", offset, pageSize); + assertEquals(3, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + + offset = 5; + page = serviceDiscovery.getInstances("A", offset, pageSize); + assertEquals(5, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + } + + @Test + public void testGetInstancesWithHealthy() { + + List instances = new LinkedList<>(asList( + new DefaultServiceInstance("A", "127.0.0.1", 8080), + new DefaultServiceInstance("A", "127.0.0.1", 8081) + )); + + + DefaultServiceInstance serviceInstance = new DefaultServiceInstance("A", "127.0.0.1", 8082); + serviceInstance.setHealthy(false); + instances.add(serviceInstance); + + instances.forEach(serviceDiscovery::register); + + // offset starts 0 + int offset = 0; + // requestSize > total elements + int requestSize = 5; + + Page page = serviceDiscovery.getInstances("A", offset, requestSize, true); + assertEquals(0, page.getOffset()); + assertEquals(5, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(2, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + // requestSize < total elements + requestSize = 2; + + offset = 1; + page = serviceDiscovery.getInstances("A", offset, requestSize, true); + assertEquals(1, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(1, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + offset = 2; + page = serviceDiscovery.getInstances("A", offset, requestSize, true); + assertEquals(2, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + + offset = 3; + page = serviceDiscovery.getInstances("A", offset, requestSize, true); + assertEquals(3, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + + offset = 5; + page = serviceDiscovery.getInstances("A", offset, requestSize, true); + assertEquals(5, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + } + + public void setServiceDiscovery(ServiceDiscovery serviceDiscovery) { + this.serviceDiscovery = serviceDiscovery; + } + + public ServiceDiscovery getServiceDiscovery() { + return serviceDiscovery; + } +} \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java new file mode 100644 index 0000000000..a588f3695a --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtilsTest.java @@ -0,0 +1,74 @@ +/* + * 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; + +import org.apache.dubbo.common.URL; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * {@link ServiceInstanceMetadataUtils} Test + * + * @since 2.7.3 + */ +public class ServiceInstanceMetadataUtilsTest { + + private static URL url = URL.valueOf("dubbo://192.168.0.102:20880/org.apache.dubbo.metadata.MetadataService?&anyhost=true&application=spring-cloud-alibaba-dubbo-provider&bind.ip=192.168.0.102&bind.port=20880&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&group=spring-cloud-alibaba-dubbo-provider&interface=org.apache.dubbo.metadata.MetadataService&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs&pid=58350®ister=true&release=2.7.1&revision=1.0.0&side=provider×tamp=1557928573174&version=1.0.0"); + private static URL url2 = URL.valueOf("rest://192.168.0.102:20880/org.apache.dubbo.metadata.MetadataService?&anyhost=true&application=spring-cloud-alibaba-dubbo-provider&bind.ip=192.168.0.102&bind.port=20880&default.deprecated=false&default.dynamic=false&default.register=true&deprecated=false&dubbo=2.0.2&dynamic=false&generic=false&group=spring-cloud-alibaba-dubbo-provider&interface=org.apache.dubbo.metadata.MetadataService&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs&pid=58350®ister=true&release=2.7.1&revision=1.0.0&side=provider×tamp=1557928573174&version=1.0.0"); + + private static final String VALUE = "{\"rest\":{\"application\":\"spring-cloud-alibaba-dubbo-provider\",\"bind.ip\":\"192.168.0.102\",\"bind.port\":\"20880\",\"dubbo\":\"2.0.2\",\"release\":\"2.7.1\",\"side\":\"provider\",\"version\":\"1.0.0\"},\"dubbo\":{\"application\":\"spring-cloud-alibaba-dubbo-provider\",\"bind.ip\":\"192.168.0.102\",\"bind.port\":\"20880\",\"dubbo\":\"2.0.2\",\"release\":\"2.7.1\",\"side\":\"provider\",\"version\":\"1.0.0\"}}"; + + @Test + public void testMetadataServiceURLParameters() { + + List urls = Arrays.asList(url, url2); + + String parameter = ServiceInstanceMetadataUtils.getMetadataServiceParameter(urls); + + assertEquals(VALUE, parameter); + } + + @Test + public void testProtocolPorts() { + +// Map metadata = new LinkedHashMap<>(); +// +// String key = protocolPortMetadataKey("dubbo"); +// assertEquals("dubbo.protocols.dubbo.port", key); +// +// metadata.put(key, "20880"); +// +// key = protocolPortMetadataKey("rest"); +// assertEquals("dubbo.protocols.rest.port", key); +// +// metadata.put(key, "8080"); +// +// Map protocolPorts = getProtocolPorts(metadata); +// +// Map expected = new LinkedHashMap<>(); +// +// expected.put("dubbo", 20880); +// expected.put("rest", 8080); +// +// assertEquals(expected, protocolPorts); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java new file mode 100644 index 0000000000..bcbd846495 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java @@ -0,0 +1,173 @@ +/* + * 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.support; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.metadata.LocalMetadataService; +import org.apache.dubbo.registry.NotifyListener; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.LinkedList; +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.apache.dubbo.common.URL.valueOf; +import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_PROTOCOL; +import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE; +import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY; +import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE; +import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY; +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; + +/** + * {@link ServiceOrientedRegistry} Test + * + * @since 2.7.3 + */ +public class ServiceOrientedRegistryTest { + + private static final URL registryURL = valueOf("in-memory://localhost:12345") + .addParameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE) + .addParameter(SUBSCRIBED_SERVICE_NAMES_KEY, "a, b , c,d,e ,"); + + private static final String SERVICE_INTERFACE = "org.apache.dubbo.metadata.MetadataService"; + + private static final String GROUP = "spring-cloud-alibaba-dubbo-provider"; + + private static final String VERSION = "1.0.0"; + + private static URL url = valueOf("dubbo://192.168.0.102:20880/" + SERVICE_INTERFACE + + "?&application=" + GROUP + + "&interface=" + SERVICE_INTERFACE + + "&group=" + GROUP + + "&version=" + VERSION + + "&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs" + + "&side=" + PROVIDER_SIDE + ); + + private static URL url2 = url.setProtocol("rest"); + + private LocalMetadataService metadataService; + + private ServiceOrientedRegistry registry; + + private NotifyListener notifyListener; + + @BeforeEach + public void init() { + registry = ServiceOrientedRegistry.create(registryURL); + metadataService = LocalMetadataService.getDefaultExtension(); + notifyListener = new MyNotifyListener(); + } + + @Test + public void testSupports() { + assertTrue(ServiceOrientedRegistry.supports(registryURL)); + } + + @Test + public void testCreate() { + assertNotNull(registry); + } + + @Test + public void testRegister() { + + registry.register(url); + + List urls = metadataService.getExportedURLs(); + + assertEquals(emptyList(), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE, GROUP), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE, GROUP, VERSION), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE, GROUP, VERSION, DEFAULT_PROTOCOL), urls); + + String serviceInterface = "com.acme.UserService"; + + URL newURL = url.setServiceInterface(serviceInterface).setPath(serviceInterface); + + registry.register(newURL); + + urls = metadataService.getExportedURLs(); + + assertEquals(metadataService.getExportedURLs(serviceInterface, GROUP, VERSION), urls); + assertEquals(metadataService.getExportedURLs(serviceInterface, GROUP, VERSION, DEFAULT_PROTOCOL), urls); + + } + + @Test + public void testUnregister() { + + String serviceInterface = "com.acme.UserService"; + + URL newURL = url.setServiceInterface(serviceInterface).setPath(serviceInterface); + + // register + registry.register(newURL); + + List urls = metadataService.getExportedURLs(); + + assertFalse(urls.isEmpty()); + assertEquals(metadataService.getExportedURLs(serviceInterface, GROUP, VERSION), urls); + assertEquals(metadataService.getExportedURLs(serviceInterface, GROUP, VERSION, DEFAULT_PROTOCOL), urls); + + // unregister + registry.unregister(newURL); + + urls = metadataService.getExportedURLs(); + + assertEquals(emptyList(), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE, GROUP), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE, GROUP, VERSION), urls); + assertEquals(metadataService.getExportedURLs(SERVICE_INTERFACE, GROUP, VERSION, DEFAULT_PROTOCOL), urls); + } + + @Test + public void testSubscribe() { + + registry.subscribe(url, new MyNotifyListener()); + + List urls = metadataService.getSubscribedURLs(); + + assertFalse(urls.isEmpty()); + assertEquals(url, urls.get(0)); + + } + + + private class MyNotifyListener implements NotifyListener { + + private List cache = new LinkedList<>(); + + @Override + public void notify(List urls) { + cache.addAll(urls); + } + + public List getURLs() { + return cache; + } + } + +} diff --git a/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..2a28074161 --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +in-memory=org.apache.dubbo.registry.client.InMemoryServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-default/src/main/java/org/apache/dubbo/registry/dubbo/DubboRegistryFactory.java b/dubbo-registry/dubbo-registry-default/src/main/java/org/apache/dubbo/registry/dubbo/DubboRegistryFactory.java index 9ddbc283c4..1da4e28001 100644 --- a/dubbo-registry/dubbo-registry-default/src/main/java/org/apache/dubbo/registry/dubbo/DubboRegistryFactory.java +++ b/dubbo-registry/dubbo-registry-default/src/main/java/org/apache/dubbo/registry/dubbo/DubboRegistryFactory.java @@ -36,19 +36,19 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; -import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; -import static org.apache.dubbo.rpc.Constants.LAZY_CONNECT_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; -import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL; import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY; +import static org.apache.dubbo.registry.Constants.CONSUMER_PROTOCOL; import static org.apache.dubbo.remoting.Constants.CONNECT_TIMEOUT_KEY; import static org.apache.dubbo.remoting.Constants.RECONNECT_KEY; import static org.apache.dubbo.rpc.Constants.CALLBACK_INSTANCES_LIMIT_KEY; +import static org.apache.dubbo.rpc.Constants.LAZY_CONNECT_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.EXPORT_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; /** * DubboRegistryFactory diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java index 5b9123ff58..f0d7cde6d8 100644 --- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java +++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/RegistryDirectoryTest.java @@ -49,19 +49,12 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; -import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK; -import static org.apache.dubbo.rpc.cluster.Constants.LOADBALANCE_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.MOCK_PROTOCOL; -import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.TYPE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; 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.DISABLED_KEY; import static org.apache.dubbo.common.constants.CommonConstants.ENABLED_KEY; import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; -import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL; @@ -70,6 +63,13 @@ import static org.apache.dubbo.common.constants.RegistryConstants.ROUTERS_CATEGO import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_PROTOCOL; import static org.apache.dubbo.rpc.Constants.$INVOKE; import static org.apache.dubbo.rpc.Constants.MOCK_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.INVOCATION_NEED_MOCK; +import static org.apache.dubbo.rpc.cluster.Constants.LOADBALANCE_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.MOCK_PROTOCOL; +import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.ROUTER_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.TYPE_KEY; import static org.junit.jupiter.api.Assertions.fail; @SuppressWarnings({"rawtypes", "unchecked"}) diff --git a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/SimpleRegistryExporter.java b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/SimpleRegistryExporter.java index c2b7c67604..dde069f127 100644 --- a/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/SimpleRegistryExporter.java +++ b/dubbo-registry/dubbo-registry-default/src/test/java/org/apache/dubbo/registry/dubbo/SimpleRegistryExporter.java @@ -27,10 +27,10 @@ import org.apache.dubbo.rpc.ProxyFactory; import java.io.IOException; import java.net.ServerSocket; -import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY; -import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL; +import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.rpc.Constants.CALLBACK_INSTANCES_LIMIT_KEY; +import static org.apache.dubbo.rpc.cluster.Constants.CLUSTER_STICKY_KEY; /** * SimpleRegistryExporter diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java index 6639962fa8..5c2a3da9aa 100644 --- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java +++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java @@ -52,13 +52,13 @@ import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.registry.Constants.ADMIN_PROTOCOL; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.DEFAULT_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.ROUTERS_CATEGORY; +import static org.apache.dubbo.registry.Constants.ADMIN_PROTOCOL; /** * Nacos {@link Registry} diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistryFactory.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistryFactory.java index 2b66fb1e95..d1630ed0d8 100644 --- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistryFactory.java +++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistryFactory.java @@ -21,23 +21,10 @@ import org.apache.dubbo.registry.Registry; import org.apache.dubbo.registry.RegistryFactory; import org.apache.dubbo.registry.support.AbstractRegistryFactory; -import com.alibaba.nacos.api.NacosFactory; -import com.alibaba.nacos.api.exception.NacosException; -import com.alibaba.nacos.api.naming.NamingService; -import com.alibaba.nacos.client.naming.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Properties; - -import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY; -import static com.alibaba.nacos.api.PropertyKeyConst.CLUSTER_NAME; -import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT; -import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE; -import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY; -import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR; -import static com.alibaba.nacos.client.naming.utils.UtilAndComs.NACOS_NAMING_LOG_NAME; -import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY; +import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.createNamingService; /** * Nacos {@link RegistryFactory} @@ -50,59 +37,6 @@ public class NacosRegistryFactory extends AbstractRegistryFactory { @Override protected Registry createRegistry(URL url) { - return new NacosRegistry(url, buildNamingService(url)); - } - - private NamingService buildNamingService(URL url) { - Properties nacosProperties = buildNacosProperties(url); - NamingService namingService; - try { - namingService = NacosFactory.createNamingService(nacosProperties); - } catch (NacosException e) { - if (logger.isErrorEnabled()) { - logger.error(e.getErrMsg(), e); - } - throw new IllegalStateException(e); - } - return namingService; - } - - private Properties buildNacosProperties(URL url) { - Properties properties = new Properties(); - setServerAddr(url, properties); - setProperties(url, properties); - return properties; - } - - private void setServerAddr(URL url, Properties properties) { - StringBuilder serverAddrBuilder = - new StringBuilder(url.getHost()) // Host - .append(":") - .append(url.getPort()); // Port - - // Append backup parameter as other servers - String backup = url.getParameter(BACKUP_KEY); - if (backup != null) { - serverAddrBuilder.append(",").append(backup); - } - - String serverAddr = serverAddrBuilder.toString(); - properties.put(SERVER_ADDR, serverAddr); - } - - private void setProperties(URL url, Properties properties) { - putPropertyIfAbsent(url, properties, NAMESPACE); - putPropertyIfAbsent(url, properties, NACOS_NAMING_LOG_NAME); - putPropertyIfAbsent(url, properties, ENDPOINT); - putPropertyIfAbsent(url, properties, ACCESS_KEY); - putPropertyIfAbsent(url, properties, SECRET_KEY); - putPropertyIfAbsent(url, properties, CLUSTER_NAME); - } - - private void putPropertyIfAbsent(URL url, Properties properties, String propertyName) { - String propertyValue = url.getParameter(propertyName); - if (StringUtils.isNotEmpty(propertyValue)) { - properties.setProperty(propertyName, propertyValue); - } + return new NacosRegistry(url, createNamingService(url)); } } diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java new file mode 100644 index 0000000000..c7a248eccd --- /dev/null +++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscovery.java @@ -0,0 +1,136 @@ +package org.apache.dubbo.registry.nacos;/* + * 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. + */ + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.function.ThrowableFunction; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; +import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; +import org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils; + +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.ListView; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.dubbo.common.function.ThrowableConsumer.execute; +import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.createNamingService; +import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.getGroup; +import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.toInstance; + +/** + * Nacos {@link ServiceDiscovery} implementation + * + * @see ServiceDiscovery + * @since 2.7.3 + */ +public class NacosServiceDiscovery implements ServiceDiscovery { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final URL connectionURL; + + private final String group; + + private NamingService namingService; + + public NacosServiceDiscovery(URL connectionURL) { + this.connectionURL = connectionURL; + this.group = getGroup(connectionURL); + } + + @Override + public void start() { + this.namingService = createNamingService(connectionURL); + } + + @Override + public void stop() { + this.namingService = null; + } + + @Override + public void register(ServiceInstance serviceInstance) throws RuntimeException { + execute(namingService, service -> { + Instance instance = toInstance(serviceInstance); + service.registerInstance(instance.getServiceName(), group, instance); + }); + } + + @Override + public void update(ServiceInstance serviceInstance) throws RuntimeException { + // TODO: Nacos should support + unregister(serviceInstance); + register(serviceInstance); + } + + @Override + public void unregister(ServiceInstance serviceInstance) throws RuntimeException { + execute(namingService, service -> { + Instance instance = toInstance(serviceInstance); + service.deregisterInstance(instance.getServiceName(), group, instance); + }); + } + + @Override + public Set getServices() { + return ThrowableFunction.execute(namingService, service -> { + ListView view = service.getServicesOfServer(0, Integer.MAX_VALUE, group); + return new LinkedHashSet<>(view.getData()); + }); + } + + @Override + public List getInstances(String serviceName) throws NullPointerException { + return ThrowableFunction.execute(namingService, service -> + service.selectInstances(serviceName, true) + .stream().map(NacosNamingServiceUtils::toServiceInstance) + .collect(Collectors.toList()) + ); + } + + @Override + public void addServiceInstancesChangedListener(String serviceName, ServiceInstancesChangedListener listener) + throws NullPointerException, IllegalArgumentException { + execute(namingService, service -> { + service.subscribe(serviceName, e -> { // Register Nacos EventListener + if (e instanceof NamingEvent) { + NamingEvent event = (NamingEvent) e; + handleEvent(event, listener); + } + }); + }); + } + + private void handleEvent(NamingEvent event, ServiceInstancesChangedListener listener) { + String serviceName = event.getServiceName(); + Collection serviceInstances = event.getInstances() + .stream() + .map(NacosNamingServiceUtils::toServiceInstance) + .collect(Collectors.toList()); + listener.onEvent(new ServiceInstancesChangedEvent(serviceName, serviceInstances)); + } +} diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryFactory.java new file mode 100644 index 0000000000..65ba64fc02 --- /dev/null +++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosServiceDiscoveryFactory.java @@ -0,0 +1,39 @@ +package org.apache.dubbo.registry.nacos;/* + * 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. + */ + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceDiscoveryFactory; + +import com.alibaba.nacos.api.naming.NamingService; + +import static org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.createNamingService; + +/** + * Nacos {@link ServiceDiscoveryFactory} + * + * @see ServiceDiscoveryFactory + * @see ServiceDiscovery + * @since 2.7.3 + */ +public class NacosServiceDiscoveryFactory implements ServiceDiscoveryFactory { + + @Override + public ServiceDiscovery create(URL connectionURL) { + return new NacosServiceDiscovery(connectionURL); + } +} diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java new file mode 100644 index 0000000000..f0d803892b --- /dev/null +++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/util/NacosNamingServiceUtils.java @@ -0,0 +1,156 @@ +package org.apache.dubbo.registry.nacos.util;/* + * 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. + */ + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstance; + +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.util.Properties; + +import static com.alibaba.nacos.api.PropertyKeyConst.ACCESS_KEY; +import static com.alibaba.nacos.api.PropertyKeyConst.CLUSTER_NAME; +import static com.alibaba.nacos.api.PropertyKeyConst.ENDPOINT; +import static com.alibaba.nacos.api.PropertyKeyConst.NAMESPACE; +import static com.alibaba.nacos.api.PropertyKeyConst.SECRET_KEY; +import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR; +import static com.alibaba.nacos.api.common.Constants.DEFAULT_GROUP; +import static com.alibaba.nacos.client.naming.utils.UtilAndComs.NACOS_NAMING_LOG_NAME; +import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY; + +/** + * The utilities class for {@link NamingService} + * + * @since 2.7.3 + */ +public class NacosNamingServiceUtils { + + private static final Logger logger = LoggerFactory.getLogger(NacosNamingServiceUtils.class); + + /** + * Convert the {@link ServiceInstance} to {@link Instance} + * + * @param serviceInstance {@link ServiceInstance} + * @return non-null + * @since 2.7.3 + */ + public static Instance toInstance(ServiceInstance serviceInstance) { + Instance instance = new Instance(); + instance.setInstanceId(serviceInstance.getId()); + instance.setServiceName(serviceInstance.getServiceName()); + instance.setIp(serviceInstance.getHost()); + instance.setPort(serviceInstance.getPort()); + instance.setMetadata(serviceInstance.getMetadata()); + instance.setEnabled(serviceInstance.isEnabled()); + instance.setHealthy(serviceInstance.isHealthy()); + return instance; + } + + /** + * Convert the {@link Instance} to {@link ServiceInstance} + * + * @param instance {@link Instance} + * @return non-null + * @since 2.7.3 + */ + public static ServiceInstance toServiceInstance(Instance instance) { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(instance.getInstanceId(), + instance.getServiceName(), instance.getIp(), instance.getPort()); + serviceInstance.setMetadata(instance.getMetadata()); + serviceInstance.setEnabled(instance.isEnabled()); + serviceInstance.setHealthy(instance.isHealthy()); + return serviceInstance; + } + + /** + * The group of {@link NamingService} to register + * + * @param connectionURL {@link URL connection url} + * @return non-null, "default" as default + * @since 2.7.3 + */ + public static String getGroup(URL connectionURL) { + return connectionURL.getParameter("nacos.group", DEFAULT_GROUP); + } + + /** + * Create an instance of {@link NamingService} from specified {@link URL connection url} + * + * @param connectionURL {@link URL connection url} + * @return {@link NamingService} + * @since 2.7.3 + */ + public static NamingService createNamingService(URL connectionURL) { + Properties nacosProperties = buildNacosProperties(connectionURL); + NamingService namingService; + try { + namingService = NacosFactory.createNamingService(nacosProperties); + } catch (NacosException e) { + if (logger.isErrorEnabled()) { + logger.error(e.getErrMsg(), e); + } + throw new IllegalStateException(e); + } + return namingService; + } + + private static Properties buildNacosProperties(URL url) { + Properties properties = new Properties(); + setServerAddr(url, properties); + setProperties(url, properties); + return properties; + } + + private static void setServerAddr(URL url, Properties properties) { + StringBuilder serverAddrBuilder = + new StringBuilder(url.getHost()) // Host + .append(":") + .append(url.getPort()); // Port + + // Append backup parameter as other servers + String backup = url.getParameter(BACKUP_KEY); + if (backup != null) { + serverAddrBuilder.append(",").append(backup); + } + + String serverAddr = serverAddrBuilder.toString(); + properties.put(SERVER_ADDR, serverAddr); + } + + private static void setProperties(URL url, Properties properties) { + putPropertyIfAbsent(url, properties, NAMESPACE); + putPropertyIfAbsent(url, properties, NACOS_NAMING_LOG_NAME); + putPropertyIfAbsent(url, properties, ENDPOINT); + putPropertyIfAbsent(url, properties, ACCESS_KEY); + putPropertyIfAbsent(url, properties, SECRET_KEY); + putPropertyIfAbsent(url, properties, CLUSTER_NAME); + } + + private static void putPropertyIfAbsent(URL url, Properties properties, String propertyName) { + String propertyValue = url.getParameter(propertyName); + if (StringUtils.isNotEmpty(propertyValue)) { + properties.setProperty(propertyName, propertyValue); + } + } +} diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..4fc3f4a4dd --- /dev/null +++ b/dubbo-registry/dubbo-registry-nacos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +nacos=org.apache.dubbo.registry.nacos.NacosServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-zookeeper/pom.xml b/dubbo-registry/dubbo-registry-zookeeper/pom.xml index 3aa13d3fcf..cf3a1a90e1 100644 --- a/dubbo-registry/dubbo-registry-zookeeper/pom.xml +++ b/dubbo-registry/dubbo-registry-zookeeper/pom.xml @@ -39,6 +39,10 @@ dubbo-remoting-zookeeper ${project.parent.version} + + org.apache.curator + curator-x-discovery + org.apache.curator curator-test diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperInstance.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperInstance.java new file mode 100644 index 0000000000..13e0f3b440 --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperInstance.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.registry.zookeeper; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents the default payload of a registered service in Zookeeper. + *

    + * It's compatible with Spring Cloud + * + * @since 2.7.3 + */ +public class ZookeeperInstance { + + private String id; + + private String name; + + private Map metadata = new HashMap<>(); + + @SuppressWarnings("unused") + private ZookeeperInstance() { + } + + public ZookeeperInstance(String id, String name, Map metadata) { + this.id = id; + this.name = name; + this.metadata = metadata; + } + + public String getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public Map getMetadata() { + return this.metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + @Override + public String toString() { + return "ZookeeperInstance{" + "id='" + this.id + '\'' + ", name='" + this.name + + '\'' + ", metadata=" + this.metadata + '}'; + } + +} diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java new file mode 100644 index 0000000000..6776a7457c --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscovery.java @@ -0,0 +1,159 @@ +/* + * 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.zookeeper; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.function.ThrowableConsumer; +import org.apache.dubbo.common.function.ThrowableFunction; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.event.EventListener; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; +import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.api.CuratorWatcher; +import org.apache.zookeeper.KeeperException; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.dubbo.common.function.ThrowableFunction.execute; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkParams.ROOT_PATH; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.build; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.buildCuratorFramework; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.buildServiceDiscovery; + +/** + * Zookeeper {@link ServiceDiscovery} implementation based on + * Apache Curator X Discovery + */ +public class ZookeeperServiceDiscovery implements ServiceDiscovery, EventListener { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final CuratorFramework curatorFramework; + + private final String rootPath; + + private final org.apache.curator.x.discovery.ServiceDiscovery serviceDiscovery; + + private final EventDispatcher dispatcher; + + /** + * The Key is watched Zookeeper path, the value is an instance of {@link CuratorWatcher} + */ + private final Map watcherCaches = new ConcurrentHashMap<>(); + + public ZookeeperServiceDiscovery(URL connectionURL) throws Exception { + this.curatorFramework = buildCuratorFramework(connectionURL); + this.rootPath = ROOT_PATH.getParameterValue(connectionURL); + this.serviceDiscovery = buildServiceDiscovery(curatorFramework, rootPath); + this.dispatcher = EventDispatcher.getDefaultExtension(); + this.dispatcher.addEventListener(this); + } + + public void register(ServiceInstance serviceInstance) throws RuntimeException { + doInServiceRegistry(serviceDiscovery -> { + serviceDiscovery.registerService(build(serviceInstance)); + }); + } + + public void update(ServiceInstance serviceInstance) throws RuntimeException { + doInServiceRegistry(serviceDiscovery -> { + serviceDiscovery.updateService(build(serviceInstance)); + }); + } + + public void unregister(ServiceInstance serviceInstance) throws RuntimeException { + doInServiceRegistry(serviceDiscovery -> { + serviceDiscovery.unregisterService(build(serviceInstance)); + }); + } + + public void start() { + doInServiceRegistry(serviceDiscovery -> { + serviceDiscovery.start(); + }); + } + + public void stop() { + doInServiceRegistry(serviceDiscovery -> { + serviceDiscovery.close(); + }); + } + + @Override + public Set getServices() { + return doInServiceDiscovery(s -> new LinkedHashSet<>(s.queryForNames())); + } + + @Override + public List getInstances(String serviceName) throws NullPointerException { + return doInServiceDiscovery(s -> build(s.queryForInstances(serviceName))); + } + + @Override + public void addServiceInstancesChangedListener(String serviceName, ServiceInstancesChangedListener listener) + throws NullPointerException, IllegalArgumentException { + registerServiceWatcher(serviceName); + dispatcher.addEventListener(listener); + } + + private void doInServiceRegistry(ThrowableConsumer consumer) { + ThrowableConsumer.execute(serviceDiscovery, s -> { + consumer.accept(s); + }); + } + + private R doInServiceDiscovery(ThrowableFunction function) { + return execute(serviceDiscovery, function); + } + + protected void registerServiceWatcher(String serviceName) { + String path = buildServicePath(serviceName); + CuratorWatcher watcher = watcherCaches.computeIfAbsent(path, key -> + new ZookeeperServiceDiscoveryChangeWatcher(this, serviceName, dispatcher)); + try { + curatorFramework.getChildren().usingWatcher(watcher).forPath(path); + } catch (KeeperException.NoNodeException e) { + // ignored + if (logger.isErrorEnabled()) { + logger.error(e.getMessage()); + } + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + private String buildServicePath(String serviceName) { + return rootPath + "/" + serviceName; + } + + @Override + public void onEvent(ServiceInstancesChangedEvent event) { + String serviceName = event.getServiceName(); + // re-register again + registerServiceWatcher(serviceName); + } +} \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java new file mode 100644 index 0000000000..5cab2d9a6e --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryChangeWatcher.java @@ -0,0 +1,68 @@ +/* + * 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.zookeeper; + +import org.apache.dubbo.event.EventDispatcher; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; + +import org.apache.curator.framework.api.CuratorWatcher; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; + +import java.util.Collection; + +import static org.apache.zookeeper.Watcher.Event.EventType.NodeChildrenChanged; +import static org.apache.zookeeper.Watcher.Event.EventType.NodeDataChanged; + +/** + * Zookeeper {@link ServiceDiscovery} Change {@link CuratorWatcher watcher} only interests in + * {@link Watcher.Event.EventType#NodeChildrenChanged} and {@link Watcher.Event.EventType#NodeDataChanged} event types, + * which will multicast a {@link ServiceInstancesChangedEvent} when the service node has been changed. + * + * @since 2.7.3 + */ +public class ZookeeperServiceDiscoveryChangeWatcher implements CuratorWatcher { + + private final ZookeeperServiceDiscovery zookeeperServiceDiscovery; + + private final String serviceName; + + private final EventDispatcher dispatcher; + + public ZookeeperServiceDiscoveryChangeWatcher(ZookeeperServiceDiscovery zookeeperServiceDiscovery, + String serviceName, EventDispatcher dispatcher) { + this.zookeeperServiceDiscovery = zookeeperServiceDiscovery; + this.serviceName = serviceName; + this.dispatcher = dispatcher; + } + + @Override + public void process(WatchedEvent event) throws Exception { + + Watcher.Event.EventType eventType = event.getType(); + + if (NodeChildrenChanged.equals(eventType) || NodeDataChanged.equals(eventType)) { + dispatcher.dispatch(new ServiceInstancesChangedEvent(serviceName, getServiceInstances(serviceName))); + } + } + + private Collection getServiceInstances(String serviceName) { + return zookeeperServiceDiscovery.getInstances(serviceName); + } +} diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryFactory.java new file mode 100644 index 0000000000..6572e28137 --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryFactory.java @@ -0,0 +1,39 @@ +/* + * 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.zookeeper; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.registry.client.ServiceDiscovery; +import org.apache.dubbo.registry.client.ServiceDiscoveryFactory; + +/** + * The zookeeper {@link ServiceDiscoveryFactory} implementation + * + * @see ServiceDiscoveryFactory + * @since 2.7.3 + */ +public class ZookeeperServiceDiscoveryFactory implements ServiceDiscoveryFactory { + + @Override + public ServiceDiscovery create(URL connectionURL) { + try { + return new ZookeeperServiceDiscovery(connectionURL); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkParams.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkParams.java new file mode 100644 index 0000000000..1156b1a4d8 --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkParams.java @@ -0,0 +1,102 @@ +/* + * 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.zookeeper.util; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.registry.client.ServiceInstance; + +import org.apache.curator.framework.CuratorFramework; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * The enumeration for the parameters of {@link CuratorFramework} + * + * @see CuratorFramework + * @since 2.7.3 + */ +public enum CuratorFrameworkParams { + + /** + * The root path of Dubbo Service + */ + ROOT_PATH("rootPath", "/services", value -> value), + + /** + * The host of current {@link ServiceInstance service instance} that will be registered + */ + INSTANCE_HOST("instanceHost", null, value -> value), + + /** + * The port of current {@link ServiceInstance service instance} that will be registered + */ + INSTANCE_PORT("instancePort", null, value -> value), + + /** + * Initial amount of time to wait between retries + */ + BASE_SLEEP_TIME("baseSleepTimeMs", 50, Integer::valueOf), + + /** + * Max number of times to retry. + */ + MAX_RETRIES("maxRetries", 10, Integer::valueOf), + + /** + * Max time in ms to sleep on each retry. + */ + MAX_SLEEP("maxSleepMs", 500, Integer::valueOf), + + /** + * Wait time to block on connection to Zookeeper. + */ + BLOCK_UNTIL_CONNECTED_WAIT("blockUntilConnectedWait", 10, Integer::valueOf), + + /** + * The unit of time related to blocking on connection to Zookeeper. + */ + BLOCK_UNTIL_CONNECTED_UNIT("blockUntilConnectedUnit", TimeUnit.SECONDS, TimeUnit::valueOf), + + ; + + private final String name; + + private final Object defaultValue; + + private final Function converter; + + CuratorFrameworkParams(String name, T defaultValue, Function converter) { + this.name = name; + this.defaultValue = defaultValue; + this.converter = (Function) converter; + } + + /** + * Get the parameter value from the specified {@link URL} + * + * @param url the Dubbo registry {@link URL} + * @param the type of value + * @return the parameter value if present, or return null + */ + public T getParameterValue(URL url) { + String param = url.getParameter(name); + Object value = param != null ? converter.apply(param) : defaultValue; + return (T) value; + } +} + diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java new file mode 100644 index 0000000000..b45830f5bf --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/util/CuratorFrameworkUtils.java @@ -0,0 +1,122 @@ +/* + * 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.zookeeper.util; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstance; +import org.apache.dubbo.registry.zookeeper.ZookeeperInstance; +import org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.curator.x.discovery.ServiceDiscovery; +import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; +import org.apache.curator.x.discovery.ServiceInstanceBuilder; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.apache.curator.x.discovery.ServiceInstance.builder; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkParams.BASE_SLEEP_TIME; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkParams.BLOCK_UNTIL_CONNECTED_UNIT; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkParams.BLOCK_UNTIL_CONNECTED_WAIT; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkParams.MAX_RETRIES; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkParams.MAX_SLEEP; + +/** + * Curator Framework Utilities Class + * + * @since 2.7.3 + */ +public abstract class CuratorFrameworkUtils { + + public static ZookeeperServiceDiscovery buildZookeeperServiceDiscovery(URL connectionURL) throws Exception { + return new ZookeeperServiceDiscovery(connectionURL); + } + + public static ServiceDiscovery buildServiceDiscovery(CuratorFramework curatorFramework, + String basePath) { + return ServiceDiscoveryBuilder.builder(ZookeeperInstance.class) + .client(curatorFramework) + .basePath(basePath) + .build(); + } + + public static CuratorFramework buildCuratorFramework(URL connectionURL) throws Exception { + CuratorFramework curatorFramework = CuratorFrameworkFactory.builder() + .connectString(connectionURL.getIp() + ":" + connectionURL.getPort()) + .retryPolicy(buildRetryPolicy(connectionURL)) + .build(); + curatorFramework.start(); + curatorFramework.blockUntilConnected(BLOCK_UNTIL_CONNECTED_WAIT.getParameterValue(connectionURL), + BLOCK_UNTIL_CONNECTED_UNIT.getParameterValue(connectionURL)); + return curatorFramework; + } + + public static RetryPolicy buildRetryPolicy(URL connectionURL) { + int baseSleepTimeMs = BASE_SLEEP_TIME.getParameterValue(connectionURL); + int maxRetries = MAX_RETRIES.getParameterValue(connectionURL); + int getMaxSleepMs = MAX_SLEEP.getParameterValue(connectionURL); + return new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries, getMaxSleepMs); + } + + + public static List build(Collection> + instances) { + return instances.stream().map(CuratorFrameworkUtils::build).collect(Collectors.toList()); + } + + public static ServiceInstance build(org.apache.curator.x.discovery.ServiceInstance instance) { + String name = instance.getName(); + String host = instance.getAddress(); + int port = instance.getPort(); + ZookeeperInstance zookeeperInstance = instance.getPayload(); + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(instance.getId(), name, host, port); + serviceInstance.setMetadata(zookeeperInstance.getMetadata()); + return serviceInstance; + } + + public static org.apache.curator.x.discovery.ServiceInstance build(ServiceInstance serviceInstance) { + ServiceInstanceBuilder builder = null; + String serviceName = serviceInstance.getServiceName(); + String host = serviceInstance.getHost(); + int port = serviceInstance.getPort(); + Map metadata = serviceInstance.getMetadata(); + String id = generateId(host, port); + ZookeeperInstance zookeeperInstance = new ZookeeperInstance(null, serviceName, metadata); + try { + builder = builder() + .id(id) + .name(serviceName) + .address(host) + .port(port) + .payload(zookeeperInstance); + } catch (Exception e) { + throw new RuntimeException(e); + } + return builder.build(); + } + + public static final String generateId(String host, int port) { + return host + ":" + port; + } +} diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..12262adad2 --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +zookeeper=org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..9e2e9ec9c9 --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java index b645408bd5..80819ec4eb 100644 --- a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java +++ b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistryTest.java @@ -37,8 +37,8 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java new file mode 100644 index 0000000000..6af604b2bb --- /dev/null +++ b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java @@ -0,0 +1,206 @@ +/* + * 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.zookeeper; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.Page; +import org.apache.dubbo.registry.client.DefaultServiceInstance; +import org.apache.dubbo.registry.client.ServiceInstance; + +import org.apache.curator.test.TestingServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import static java.util.Arrays.asList; +import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.buildZookeeperServiceDiscovery; +import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.generateId; +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 ZookeeperServiceDiscovery} Test + * + * @since 2.7.3 + */ +public class ZookeeperServiceDiscoveryTest { + + private static final String SERVICE_NAME = "A"; + + private static final String LOCALHOST = "127.0.0.1"; + + private TestingServer zkServer; + private int zkServerPort; + private URL registryUrl; + + private ZookeeperServiceDiscovery discovery; + + @BeforeEach + public void init() throws Exception { + zkServerPort = getAvailablePort(); + zkServer = new TestingServer(zkServerPort, true); + zkServer.start(); + + this.registryUrl = URL.valueOf("zookeeper://127.0.0.1:" + zkServerPort); + this.discovery = buildZookeeperServiceDiscovery(registryUrl); + this.discovery.start(); + } + + @AfterEach + public void close() throws IOException { + discovery.stop(); + zkServer.stop(); + } + + @Test + public void testRegistration() { + + DefaultServiceInstance serviceInstance = createServiceInstance(SERVICE_NAME, LOCALHOST, 8080); + + discovery.register(serviceInstance); + + List serviceInstances = discovery.getInstances(SERVICE_NAME); + + assertTrue(serviceInstances.contains(serviceInstance)); + assertEquals(asList(serviceInstance), serviceInstances); + + Map metadata = new HashMap<>(); + metadata.put("message", "Hello,World"); + serviceInstance.setMetadata(metadata); + + discovery.update(serviceInstance); + + serviceInstances = discovery.getInstances(SERVICE_NAME); + + assertEquals(serviceInstance, serviceInstances.get(0)); + + discovery.unregister(serviceInstance); + + serviceInstances = discovery.getInstances(SERVICE_NAME); + + assertTrue(serviceInstances.isEmpty()); + } + + private DefaultServiceInstance createServiceInstance(String serviceName, String host, int port) { + return new DefaultServiceInstance(generateId(host, port), serviceName, host, port); + } + + @Test + public void testGetInstances() throws InterruptedException { + + List instances = asList( + createServiceInstance(SERVICE_NAME, LOCALHOST, 8080), + createServiceInstance(SERVICE_NAME, LOCALHOST, 8081), + createServiceInstance(SERVICE_NAME, LOCALHOST, 8082) + ); + + instances.forEach(discovery::register); + + List serviceInstances = new LinkedList<>(); + + CountDownLatch latch = new CountDownLatch(1); + + // Add Listener + discovery.addServiceInstancesChangedListener(SERVICE_NAME, event -> { + serviceInstances.addAll(event.getServiceInstances()); + latch.countDown(); + }); + + discovery.register(createServiceInstance(SERVICE_NAME, LOCALHOST, 8082)); + discovery.update(createServiceInstance(SERVICE_NAME, LOCALHOST, 8082)); + + latch.await(); + + assertFalse(serviceInstances.isEmpty()); + + // offset starts 0 + int offset = 0; + // requestSize > total elements + int requestSize = 5; + + Page page = discovery.getInstances(SERVICE_NAME, offset, requestSize); + assertEquals(0, page.getOffset()); + assertEquals(5, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(3, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + // requestSize < total elements + requestSize = 2; + + page = discovery.getInstances(SERVICE_NAME, offset, requestSize); + assertEquals(0, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(2, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + offset = 1; + page = discovery.getInstances(SERVICE_NAME, offset, requestSize); + assertEquals(1, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(2, page.getData().size()); + assertTrue(page.hasData()); + + for (ServiceInstance instance : page.getData()) { + assertTrue(instances.contains(instance)); + } + + offset = 2; + page = discovery.getInstances(SERVICE_NAME, offset, requestSize); + assertEquals(2, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(1, page.getData().size()); + assertTrue(page.hasData()); + + offset = 3; + page = discovery.getInstances(SERVICE_NAME, offset, requestSize); + assertEquals(3, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + + offset = 5; + page = discovery.getInstances(SERVICE_NAME, offset, requestSize); + assertEquals(5, page.getOffset()); + assertEquals(2, page.getPageSize()); + assertEquals(3, page.getTotalSize()); + assertEquals(0, page.getData().size()); + assertFalse(page.hasData()); + + } +} diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java index 5a010faf52..65c3db7fe6 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeClient.java @@ -33,11 +33,11 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import static org.apache.dubbo.remoting.utils.UrlUtils.getHeartbeat; -import static org.apache.dubbo.remoting.utils.UrlUtils.getIdleTimeout; import static org.apache.dubbo.remoting.Constants.HEARTBEAT_CHECK_TICK; import static org.apache.dubbo.remoting.Constants.LEAST_HEARTBEAT_DURATION; import static org.apache.dubbo.remoting.Constants.TICKS_PER_WHEEL; +import static org.apache.dubbo.remoting.utils.UrlUtils.getHeartbeat; +import static org.apache.dubbo.remoting.utils.UrlUtils.getIdleTimeout; /** * DefaultMessageClient diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartBeatTaskTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartBeatTaskTest.java index 3249d46f16..21009a0262 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartBeatTaskTest.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartBeatTaskTest.java @@ -21,6 +21,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.timer.HashedWheelTimer; import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.exchange.Request; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java index 13e0c4ef83..681e834b17 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClient.java @@ -40,10 +40,10 @@ import io.netty.handler.proxy.Socks5ProxyHandler; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.concurrent.DefaultThreadFactory; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - import java.net.InetSocketAddress; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + /** * NettyClient. */ diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ClientReconnectTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ClientReconnectTest.java index 51c4907b36..f28ff55e4e 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ClientReconnectTest.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ClientReconnectTest.java @@ -25,6 +25,7 @@ import org.apache.dubbo.remoting.RemotingException; import org.apache.dubbo.remoting.Server; import org.apache.dubbo.remoting.exchange.Exchangers; import org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java index eb6cebed61..51e22b9594 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ContextFilter.java @@ -35,8 +35,8 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_APPLICATION_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.rpc.Constants.ASYNC_KEY; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; +import static org.apache.dubbo.rpc.Constants.ASYNC_KEY; import static org.apache.dubbo.rpc.Constants.FORCE_USE_TAG; import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/MockInvoker.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/MockInvoker.java index 90f4d69b97..216be3768e 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/MockInvoker.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/support/MockInvoker.java @@ -39,12 +39,12 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static org.apache.dubbo.rpc.Constants.MOCK_KEY; -import static org.apache.dubbo.rpc.Constants.RETURN_PREFIX; -import static org.apache.dubbo.rpc.Constants.THROW_PREFIX; import static org.apache.dubbo.rpc.Constants.FAIL_PREFIX; import static org.apache.dubbo.rpc.Constants.FORCE_PREFIX; +import static org.apache.dubbo.rpc.Constants.MOCK_KEY; import static org.apache.dubbo.rpc.Constants.RETURN_KEY; +import static org.apache.dubbo.rpc.Constants.RETURN_PREFIX; +import static org.apache.dubbo.rpc.Constants.THROW_PREFIX; final public class MockInvoker implements Invoker { private final static ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericFilterTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericFilterTest.java index b571bd1de3..3114964384 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericFilterTest.java @@ -36,11 +36,11 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import static org.apache.dubbo.rpc.Constants.$INVOKE; +import static org.apache.dubbo.rpc.Constants.GENERIC_KEY; +import static org.apache.dubbo.rpc.Constants.GENERIC_SERIALIZATION_NATIVE_JAVA; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import static org.apache.dubbo.rpc.Constants.$INVOKE; -import static org.apache.dubbo.rpc.Constants.GENERIC_SERIALIZATION_NATIVE_JAVA; -import static org.apache.dubbo.rpc.Constants.GENERIC_KEY; public class GenericFilterTest { GenericFilter genericFilter = new GenericFilter(); diff --git a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericImplFilterTest.java b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericImplFilterTest.java index 7145aa08ab..abf2f0cfca 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericImplFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-api/src/test/java/org/apache/dubbo/rpc/filter/GenericImplFilterTest.java @@ -36,11 +36,10 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.when; - import static org.apache.dubbo.rpc.Constants.$INVOKE; import static org.apache.dubbo.rpc.Constants.GENERIC_KEY; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; public class GenericImplFilterTest { diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java index c82599c3a1..de66561ee4 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/CallbackServiceCodec.java @@ -41,13 +41,13 @@ 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.METHODS_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CALLBACK_SERVICE_KEY; import static org.apache.dubbo.rpc.Constants.CALLBACK_INSTANCES_LIMIT_KEY; import static org.apache.dubbo.rpc.Constants.DEFAULT_CALLBACK_INSTANCES; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CALLBACK_SERVICE_PROXY_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.IS_CALLBACK_SERVICE; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CHANNEL_CALLBACK_KEY; import static org.apache.dubbo.rpc.Constants.IS_SERVER_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CALLBACK_SERVICE_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CALLBACK_SERVICE_PROXY_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CHANNEL_CALLBACK_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.IS_CALLBACK_SERVICE; /** * callback service helper diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java index 69a0629c4e..f0c45b913d 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java @@ -39,8 +39,8 @@ import java.util.Map; import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument; import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument; public class DecodeableRpcInvocation extends RpcInvocation implements Codec, Decodeable { diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodec.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodec.java index dd3e847748..d101d798fc 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodec.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboCodec.java @@ -40,10 +40,10 @@ import java.io.InputStream; import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; import static org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.encodeInvocationArgument; import static org.apache.dubbo.rpc.protocol.dubbo.Constants.DECODE_IN_IO_THREAD_KEY; import static org.apache.dubbo.rpc.protocol.dubbo.Constants.DEFAULT_DECODE_IN_IO_THREAD; -import static org.apache.dubbo.remoting.Constants.DUBBO_VERSION_KEY; /** * Dubbo codec. diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java index 3cc20237f3..529ac47b29 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboProtocol.java @@ -62,27 +62,27 @@ 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.PATH_KEY; import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; -import static org.apache.dubbo.rpc.Constants.LAZY_CONNECT_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.ON_CONNECT_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.ON_DISCONNECT_KEY; -import static org.apache.dubbo.remoting.Constants.DEFAULT_HEARTBEAT; -import static org.apache.dubbo.remoting.Constants.HEARTBEAT_KEY; import static org.apache.dubbo.remoting.Constants.CHANNEL_READONLYEVENT_SENT_KEY; import static org.apache.dubbo.remoting.Constants.CLIENT_KEY; import static org.apache.dubbo.remoting.Constants.CODEC_KEY; +import static org.apache.dubbo.remoting.Constants.CONNECTIONS_KEY; +import static org.apache.dubbo.remoting.Constants.DEFAULT_HEARTBEAT; import static org.apache.dubbo.remoting.Constants.DEFAULT_REMOTING_CLIENT; +import static org.apache.dubbo.remoting.Constants.HEARTBEAT_KEY; import static org.apache.dubbo.remoting.Constants.SERVER_KEY; import static org.apache.dubbo.rpc.Constants.DEFAULT_REMOTING_SERVER; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CALLBACK_SERVICE_KEY; -import static org.apache.dubbo.remoting.Constants.CONNECTIONS_KEY; import static org.apache.dubbo.rpc.Constants.DEFAULT_STUB_EVENT; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.IS_CALLBACK_SERVICE; import static org.apache.dubbo.rpc.Constants.IS_SERVER_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.OPTIMIZER_KEY; +import static org.apache.dubbo.rpc.Constants.LAZY_CONNECT_KEY; import static org.apache.dubbo.rpc.Constants.STUB_EVENT_KEY; import static org.apache.dubbo.rpc.Constants.STUB_EVENT_METHODS_KEY; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.SHARE_CONNECTIONS_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.CALLBACK_SERVICE_KEY; import static org.apache.dubbo.rpc.protocol.dubbo.Constants.DEFAULT_SHARE_CONNECTIONS; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.IS_CALLBACK_SERVICE; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.ON_CONNECT_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.ON_DISCONNECT_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.OPTIMIZER_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.SHARE_CONNECTIONS_KEY; /** diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java index 5297280177..121416bd00 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java @@ -41,8 +41,8 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; -import static org.apache.dubbo.rpc.protocol.dubbo.Constants.SHARE_CONNECTIONS_KEY; import static org.apache.dubbo.remoting.Constants.CONNECTIONS_KEY; +import static org.apache.dubbo.rpc.protocol.dubbo.Constants.SHARE_CONNECTIONS_KEY; public class ReferenceCountExchangeClientTest { diff --git a/dubbo-rpc/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java b/dubbo-rpc/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java index dddd724cf8..ae7b557ceb 100644 --- a/dubbo-rpc/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java +++ b/dubbo-rpc/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java @@ -17,8 +17,8 @@ package org.apache.dubbo.rpc.protocol.http; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.common.Version; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.http.HttpBinder; import org.apache.dubbo.remoting.http.HttpHandler; diff --git a/dubbo-rpc/dubbo-rpc-xml/README.md b/dubbo-rpc/dubbo-rpc-xml/README.md index 17c022c5fa..67cec24638 100644 --- a/dubbo-rpc/dubbo-rpc-xml/README.md +++ b/dubbo-rpc/dubbo-rpc-xml/README.md @@ -8,7 +8,7 @@ A RPC Extension for XML-RPC(http://ws.apache.org/xmlrpc) org.apache.dubbo dubbo-rpc - 2.7.2-SNAPSHOT + 2.7.3-SNAPSHOT ``` diff --git a/dubbo-rpc/dubbo-rpc-xml/src/main/java/org/apache/dubbo/xml/rpc/protocol/xmlrpc/XmlRpcProtocol.java b/dubbo-rpc/dubbo-rpc-xml/src/main/java/org/apache/dubbo/xml/rpc/protocol/xmlrpc/XmlRpcProtocol.java index acda3dea48..0b29784168 100644 --- a/dubbo-rpc/dubbo-rpc-xml/src/main/java/org/apache/dubbo/xml/rpc/protocol/xmlrpc/XmlRpcProtocol.java +++ b/dubbo-rpc/dubbo-rpc-xml/src/main/java/org/apache/dubbo/xml/rpc/protocol/xmlrpc/XmlRpcProtocol.java @@ -42,7 +42,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class XmlRpcProtocol extends AbstractProxyProtocol { - + public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin"; public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods"; public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers"; @@ -67,7 +67,7 @@ public class XmlRpcProtocol extends AbstractProxyProtocol { } private class InternalHandler implements HttpHandler { - + private boolean cors; public InternalHandler(boolean cors) { @@ -90,7 +90,7 @@ public class XmlRpcProtocol extends AbstractProxyProtocol { RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); try { - xmlrpc.execute (request,response); + xmlrpc.execute(request, response); } catch (Throwable e) { throw new ServletException(e); } @@ -117,19 +117,19 @@ public class XmlRpcProtocol extends AbstractProxyProtocol { PropertyHandlerMapping propertyHandlerMapping = new PropertyHandlerMapping(); try { - propertyHandlerMapping.setRequestProcessorFactoryFactory(new RequestProcessorFactoryFactory(){ + propertyHandlerMapping.setRequestProcessorFactoryFactory(new RequestProcessorFactoryFactory() { @Override - public RequestProcessorFactory getRequestProcessorFactory(Class pClass) throws XmlRpcException{ - return new RequestProcessorFactory(){ + public RequestProcessorFactory getRequestProcessorFactory(Class pClass) throws XmlRpcException { + return new RequestProcessorFactory() { @Override - public Object getRequestProcessor(XmlRpcRequest pRequest) throws XmlRpcException{ + public Object getRequestProcessor(XmlRpcRequest pRequest) throws XmlRpcException { return impl; } }; } }); - propertyHandlerMapping.addHandler(XmlRpcProxyFactoryBean.replace(type.getName()),type); + propertyHandlerMapping.addHandler(XmlRpcProxyFactoryBean.replace(type.getName()), type); } catch (Exception e) { throw new RpcException(e); @@ -149,8 +149,6 @@ public class XmlRpcProtocol extends AbstractProxyProtocol { }; } - @Override - @SuppressWarnings("unchecked") @Override protected T doRefer(final Class serviceType, URL url) throws RpcException { XmlRpcProxyFactoryBean xmlRpcProxyFactoryBean = new XmlRpcProxyFactoryBean(); diff --git a/pom.xml b/pom.xml index 16afc2714f..084674489c 100644 --- a/pom.xml +++ b/pom.xml @@ -125,7 +125,7 @@ true true - 2.7.2-SNAPSHOT + 2.7.3-SNAPSHOT @@ -149,6 +149,9 @@ dubbo-metadata-report dubbo-configcenter dubbo-dependencies + dubbo-event + dubbo-metadata + dubbo-bootstrap