/*
 * 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.spring;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.config.spring.action.DemoActionByAnnotation;
import org.apache.dubbo.config.spring.action.DemoActionBySetter;
import org.apache.dubbo.config.spring.annotation.consumer.AnnotationAction;
import org.apache.dubbo.config.spring.api.DemoService;
import org.apache.dubbo.config.spring.api.HelloService;
import org.apache.dubbo.config.spring.context.annotation.provider.ProviderConfiguration;
import org.apache.dubbo.config.spring.filter.MockFilter;
import org.apache.dubbo.config.spring.impl.DemoServiceImpl;
import org.apache.dubbo.config.spring.impl.HelloServiceImpl;
import org.apache.dubbo.config.spring.registry.MockRegistry;
import org.apache.dubbo.config.spring.registry.MockRegistryFactory;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.RegistryService;
import org.apache.dubbo.rpc.Exporter;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.service.GenericService;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.annotation.DirtiesContext;

import java.util.Collection;
import java.util.List;

import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_SERIALIZATION_BEAN;
import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;


/**
 * ConfigTest
 */
@Disabled
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ConfigTest {

    @Test
    public void testSpringExtensionInject() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/spring-extension-inject.xml");
        ctx.start();
        try {
            MockFilter filter = (MockFilter) ExtensionLoader.getExtensionLoader(Filter.class).getExtension("mymock");
            assertNotNull(filter.getMockDao());
            assertNotNull(filter.getProtocol());
            assertNotNull(filter.getLoadBalance());
        } finally {
            ctx.stop();
            ctx.close();
        }
    }

    @Test
    public void testServiceClass() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/service-class.xml");
        ctx.start();
        try {
            DemoService demoService = refer("dubbo://127.0.0.1:30887");
            String hello = demoService.sayName("hello");
            assertEquals("welcome:hello", hello);
        } finally {
            ctx.stop();
            ctx.close();
        }
    }

    @Test
    public void testServiceAnnotation() {
        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
        providerContext.register(ProviderConfiguration.class);

        providerContext.refresh();

        ReferenceConfig<HelloService> reference = new ReferenceConfig<HelloService>();
        reference.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        reference.setInterface(HelloService.class);
        reference.setUrl("dubbo://127.0.0.1:12345");

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(new ApplicationConfig("consumer"))
                .reference(reference)
                .start();
        HelloService helloService = bootstrap.getCache().get(reference);

        String hello = helloService.sayHello("hello");
        assertEquals("Hello, hello", hello);

    }

    @Test
    @SuppressWarnings("unchecked")
    public void testProviderNestedService() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/provider-nested-service.xml");
        ctx.start();
        try {
            ServiceConfig<DemoService> serviceConfig = (ServiceConfig<DemoService>) ctx.getBean("serviceConfig");
            assertNotNull(serviceConfig.getProvider());
            assertEquals(2000, serviceConfig.getProvider().getTimeout().intValue());

            ServiceConfig<DemoService> serviceConfig2 = (ServiceConfig<DemoService>) ctx.getBean("serviceConfig2");
            assertNotNull(serviceConfig2.getProvider());
            assertEquals(1000, serviceConfig2.getProvider().getTimeout().intValue());
        } finally {
            ctx.stop();
            ctx.close();
        }
    }

    private DemoService refer(String url) {
        ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
        reference.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        reference.setInterface(DemoService.class);
        reference.setUrl(url);

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(new ApplicationConfig("consumer"))
                .reference(reference)
                .start();
        return bootstrap.getCache().get(reference);
    }

    @Test
    public void testToString() {
        ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
        reference.setApplication(new ApplicationConfig("consumer"));
        reference.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        reference.setInterface(DemoService.class);
        reference.setUrl("dubbo://127.0.0.1:20881");
        String str = reference.toString();
        assertTrue(str.startsWith("<dubbo:reference "));
        assertTrue(str.contains(" url=\"dubbo://127.0.0.1:20881\" "));
        assertTrue(str.contains(" interface=\"org.apache.dubbo.config.spring.api.DemoService\" "));
        assertTrue(str.endsWith(" />"));
    }

    @Test
    public void testForks() {
        ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
        reference.setApplication(new ApplicationConfig("consumer"));
        reference.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        reference.setInterface(DemoService.class);
        reference.setUrl("dubbo://127.0.0.1:20881");

        int forks = 10;
        reference.setForks(forks);
        String str = reference.toString();
        assertTrue(str.contains("forks=\"" + forks + "\""));
    }

    @Test
    public void testMultiProtocol() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/multi-protocol.xml");
        ctx.start();
        try {
            DemoService demoService = refer("dubbo://127.0.0.1:20881");
            String hello = demoService.sayName("hello");
            assertEquals("say:hello", hello);
        } finally {
            ctx.stop();
            ctx.close();
        }
    }

    @Test
    public void testMultiProtocolDefault() {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/multi-protocol-default.xml");
        ctx.start();
        try {
            DemoService demoService = refer("rmi://127.0.0.1:10991");
            String hello = demoService.sayName("hello");
            assertEquals("say:hello", hello);
        } finally {
            ctx.stop();
            ctx.close();
        }
    }

    @Test
    public void testMultiProtocolError() {
        try {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/multi-protocol-error.xml");
            ctx.start();
            ctx.stop();
            ctx.close();
        } catch (BeanCreationException e) {
            assertTrue(e.getMessage().contains("Found multi-protocols"));
        }
    }

    @Test
    public void testMultiProtocolRegister() {
        SimpleRegistryService registryService = new SimpleRegistryService();
        Exporter<RegistryService> exporter = SimpleRegistryExporter.export(4547, registryService);
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/multi-protocol-register.xml");
        ctx.start();
        try {
            List<URL> urls = registryService.getRegistered().get("org.apache.dubbo.config.spring.api.DemoService");
            assertNotNull(urls);
            assertEquals(1, urls.size());
            assertEquals("dubbo://" + NetUtils.getLocalHost() + ":20824/org.apache.dubbo.config.spring.api.DemoService", urls.get(0).toIdentityString());
        } finally {
            ctx.stop();
            ctx.close();
            exporter.unexport();
        }
    }

    @Test
    public void testMultiRegistry() {
        SimpleRegistryService registryService1 = new SimpleRegistryService();
        Exporter<RegistryService> exporter1 = SimpleRegistryExporter.export(4545, registryService1);
        SimpleRegistryService registryService2 = new SimpleRegistryService();
        Exporter<RegistryService> exporter2 = SimpleRegistryExporter.export(4546, registryService2);
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/multi-registry.xml");
        ctx.start();
        try {
            List<URL> urls1 = registryService1.getRegistered().get("org.apache.dubbo.config.spring.api.DemoService");
            assertNull(urls1);
            List<URL> urls2 = registryService2.getRegistered().get("org.apache.dubbo.config.spring.api.DemoService");
            assertNotNull(urls2);
            assertEquals(1, urls2.size());
            assertEquals("dubbo://" + NetUtils.getLocalHost() + ":20880/org.apache.dubbo.config.spring.api.DemoService", urls2.get(0).toIdentityString());
        } finally {
            ctx.stop();
            ctx.close();
            exporter1.unexport();
            exporter2.unexport();
        }
    }

    @Test
    public void testDelayFixedTime() throws Exception {
        SimpleRegistryService registryService = new SimpleRegistryService();
        Exporter<RegistryService> exporter = SimpleRegistryExporter.export(4548, registryService);
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/delay-fixed-time.xml");
        ctx.start();
        try {
            List<URL> urls = registryService.getRegistered().get("org.apache.dubbo.config.spring.api.DemoService");
            assertNull(urls);
            int i = 0;
            while ((i++) < 60 && urls == null) {
                urls = registryService.getRegistered().get("org.apache.dubbo.config.spring.api.DemoService");
                Thread.sleep(10);
            }
            assertNotNull(urls);
            assertEquals(1, urls.size());
            assertEquals("dubbo://" + NetUtils.getLocalHost() + ":20888/org.apache.dubbo.config.spring.api.DemoService", urls.get(0).toIdentityString());
        } finally {
            ctx.stop();
            ctx.close();
            exporter.unexport();
        }
    }

    @Test
    public void testDelayOnInitialized() throws Exception {
        SimpleRegistryService registryService = new SimpleRegistryService();
        Exporter<RegistryService> exporter = SimpleRegistryExporter.export(4548, registryService);
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/delay-on-initialized.xml");
        //ctx.start();
        try {
            List<URL> urls = registryService.getRegistered().get("org.apache.dubbo.config.spring.api.DemoService");
            assertNotNull(urls);
            assertEquals(1, urls.size());
            assertEquals("dubbo://" + NetUtils.getLocalHost() + ":20888/org.apache.dubbo.config.spring.api.DemoService", urls.get(0).toIdentityString());
        } finally {
            ctx.stop();
            ctx.close();
            exporter.unexport();
        }
    }

    @Test
    public void testRmiTimeout() throws Exception {
        if (System.getProperty("sun.rmi.transport.tcp.responseTimeout") != null) {
            System.setProperty("sun.rmi.transport.tcp.responseTimeout", "");
        }
        ConsumerConfig consumer = new ConsumerConfig();
        consumer.setTimeout(1000);
        assertEquals("1000", System.getProperty("sun.rmi.transport.tcp.responseTimeout"));
        consumer.setTimeout(2000);
        assertEquals("1000", System.getProperty("sun.rmi.transport.tcp.responseTimeout"));
    }

    @Test
    public void testAutowireAndAOP() throws Exception {
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/demo-provider.xml");
        providerContext.start();
        try {
            ClassPathXmlApplicationContext byNameContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/aop-autowire-byname.xml");
            byNameContext.start();
            try {
                DemoActionBySetter demoActionBySetter = (DemoActionBySetter) byNameContext.getBean("demoActionBySetter");
                assertNotNull(demoActionBySetter.getDemoService());
                assertEquals("aop:say:hello", demoActionBySetter.getDemoService().sayName("hello"));
                DemoActionByAnnotation demoActionByAnnotation = (DemoActionByAnnotation) byNameContext.getBean("demoActionByAnnotation");
                assertNotNull(demoActionByAnnotation.getDemoService());
                assertEquals("aop:say:hello", demoActionByAnnotation.getDemoService().sayName("hello"));
            } finally {
                byNameContext.stop();
                byNameContext.close();
            }
            ClassPathXmlApplicationContext byTypeContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/aop-autowire-bytype.xml");
            byTypeContext.start();
            try {
                DemoActionBySetter demoActionBySetter = (DemoActionBySetter) byTypeContext.getBean("demoActionBySetter");
                assertNotNull(demoActionBySetter.getDemoService());
                assertEquals("aop:say:hello", demoActionBySetter.getDemoService().sayName("hello"));
                DemoActionByAnnotation demoActionByAnnotation = (DemoActionByAnnotation) byTypeContext.getBean("demoActionByAnnotation");
                assertNotNull(demoActionByAnnotation.getDemoService());
                assertEquals("aop:say:hello", demoActionByAnnotation.getDemoService().sayName("hello"));
            } finally {
                byTypeContext.stop();
                byTypeContext.close();
            }
        } finally {
            providerContext.stop();
            providerContext.close();
        }
    }

    @Test
    public void testAppendFilter() throws Exception {
        ApplicationConfig application = new ApplicationConfig("provider");

        ProviderConfig provider = new ProviderConfig();
        provider.setFilter("classloader,monitor");

        ConsumerConfig consumer = new ConsumerConfig();
        consumer.setFilter("classloader,monitor");

        ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
        service.setFilter("accesslog,trace");
        service.setProvider(provider);
        service.setProtocol(new ProtocolConfig("dubbo", 20880));
        service.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());

        ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
        reference.setFilter("accesslog,trace");
        reference.setConsumer(consumer);
        reference.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        reference.setInterface(DemoService.class);
        reference.setUrl("dubbo://" + NetUtils.getLocalHost() + ":20880?" + DemoService.class.getName() + "?check=false");

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(application)
                .provider(provider)
                .service(service)
                .reference(reference)
                .start();

        try {
            List<URL> urls = service.getExportedUrls();
            assertNotNull(urls);
            assertEquals(1, urls.size());
            assertEquals("classloader,monitor,accesslog,trace", urls.get(0).getParameter("service.filter"));

            urls = reference.getExportedUrls();
            assertNotNull(urls);
            assertEquals(1, urls.size());
            assertEquals("classloader,monitor,accesslog,trace", urls.get(0).getParameter("reference.filter"));

        } finally {
            bootstrap.stop();
        }
    }

    @Test
    public void testInitReference() throws Exception {
        String configPath = ConfigTest.class.getPackage().getName().replace('.', '/');
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(configPath + "/demo-provider.xml", configPath+"/demo-provider-properties.xml");
        providerContext.start();
        try {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(configPath + "/init-reference.xml");
            ctx.start();
            try {

                ReferenceBean referenceBean = ctx.getBean("&demoService", ReferenceBean.class);
                Assertions.assertEquals("demo_tag", referenceBean.getTag());

                DemoService demoService = (DemoService) ctx.getBean("demoService");
                assertEquals("say:world", demoService.sayName("world"));
            } finally {
                ctx.stop();
                ctx.close();
            }
        } finally {
            providerContext.stop();
            providerContext.close();
        }
    }

    // DUBBO-571 methods key in provider's URLONE doesn't contain the methods from inherited super interface
    @Test
    public void test_noMethodInterface_methodsKeyHasValue() throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/demo-provider-no-methods-interface.xml");
        ctx.start();
        try {
            ServiceBean bean = (ServiceBean) ctx.getBean("service");
            List<URL> urls = bean.getExportedUrls();
            assertEquals(1, urls.size());
            URL url = urls.get(0);
            assertEquals("sayName,getBox", url.getParameter("methods"));
        } finally {
            ctx.stop();
            ctx.close();
        }
    }

    // DUBBO-147 find all invoker instances which have been tried from RpcContext
    @Test
    public void test_RpcContext_getUrls() throws Exception {
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(
                ConfigTest.class.getPackage().getName().replace('.', '/') + "/demo-provider-long-waiting.xml");
        providerContext.start();

        try {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                    ConfigTest.class.getPackage().getName().replace('.', '/')
                            + "/init-reference-getUrls.xml");
            ctx.start();
            try {
                DemoService demoService = (DemoService) ctx.getBean("demoService");
                try {
                    demoService.sayName("Haha");
                    fail();
                } catch (RpcException expected) {
                    assertThat(expected.getMessage(), containsString("Tried 3 times"));
                }

                assertEquals(3, RpcContext.getContext().getUrls().size());
            } finally {
                ctx.stop();
                ctx.close();
            }
        } finally {
            providerContext.stop();
            providerContext.close();
        }
    }

    // BUG: DUBBO-846 in version 2.0.9, config retry="false" on provider's method doesn't work
    @Test
    public void test_retrySettingFail() throws Exception {
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(
                ConfigTest.class.getPackage().getName().replace('.', '/') + "/demo-provider-long-waiting.xml");
        providerContext.start();

        try {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                    ConfigTest.class.getPackage().getName().replace('.', '/')
                            + "/init-reference-retry-false.xml");
            ctx.start();
            try {
                DemoService demoService = (DemoService) ctx.getBean("demoService");
                try {
                    demoService.sayName("Haha");
                    fail();
                } catch (RpcException expected) {
                    assertThat(expected.getMessage(), containsString("Tried 1 times"));
                }

                assertEquals(1, RpcContext.getContext().getUrls().size());
            } finally {
                ctx.stop();
                ctx.close();
            }
        } finally {
            providerContext.stop();
            providerContext.close();
        }
    }

    // BuG: DUBBO-146 Provider doesn't have exception output, and consumer has timeout error when serialization fails
    // for example, object transported on the wire doesn't implement Serializable
    @Test
    public void test_returnSerializationFail() throws Exception {
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/demo-provider-UnserializableBox.xml");
        providerContext.start();
        try {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/init-reference.xml");
            ctx.start();
            try {
                DemoService demoService = (DemoService) ctx.getBean("demoService");
                try {
                    demoService.getBox();
                    fail();
                } catch (RpcException expected) {
                    assertThat(expected.getMessage(), containsString("must implement java.io.Serializable"));
                }
            } finally {
                ctx.stop();
                ctx.close();
            }
        } finally {
            providerContext.stop();
            providerContext.close();
        }
    }

    @Test
    public void testXmlOverrideProperties() throws Exception {
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/xml-override-properties.xml");
        providerContext.start();
        try {
            ApplicationConfig application = (ApplicationConfig) providerContext.getBean("application");
            assertEquals("demo-provider", application.getName());
            assertEquals("world", application.getOwner());

            RegistryConfig registry = (RegistryConfig) providerContext.getBean("registry");
            assertEquals("N/A", registry.getAddress());

            ProtocolConfig dubbo = (ProtocolConfig) providerContext.getBean("dubbo");
            assertEquals(20813, dubbo.getPort().intValue());

        } finally {
            providerContext.stop();
            providerContext.close();
        }
    }

    @Test
    public void testApiOverrideProperties() throws Exception {
        ApplicationConfig application = new ApplicationConfig();
        application.setName("api-override-properties");

        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("N/A");

        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(13123);

        ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
        service.setInterface(DemoService.class);
        service.setRef(new DemoServiceImpl());
        service.setRegistry(registry);
        service.setProtocol(protocol);

        ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
        reference.setRegistry(new RegistryConfig(RegistryConfig.NO_AVAILABLE));
        reference.setInterface(DemoService.class);
        reference.setUrl("dubbo://127.0.0.1:13123");

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(application)
                .registry(registry)
                .protocol(protocol)
                .service(service)
                .reference(reference)
                .start();

        try {
            URL url = service.getExportedUrls().get(0);
            assertEquals("api-override-properties", url.getParameter("application"));
            assertEquals("world", url.getParameter("owner"));
            assertEquals(13123, url.getPort());

            url = reference.getExportedUrls().get(0);
            assertEquals("2000", url.getParameter("timeout"));
        } finally {
            bootstrap.stop();
        }
    }

    @Test
    public void testSystemPropertyOverrideProtocol() throws Exception {
        System.setProperty("dubbo.protocol.port", "20812");
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/override-protocol.xml");
        providerContext.start();
        try {
            ProtocolConfig dubbo = (ProtocolConfig) providerContext.getBean("dubbo");
            assertEquals(20812, dubbo.getPort().intValue());
        } finally {
            System.setProperty("dubbo.protocol.port", "");
            providerContext.stop();
            providerContext.close();
        }
    }

    @Test
    public void testSystemPropertyOverrideMultiProtocol() throws Exception {
        System.setProperty("dubbo.protocol.dubbo.port", "20814");
        System.setProperty("dubbo.protocol.rmi.port", "10914");
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/override-multi-protocol.xml");
        providerContext.start();
        try {
            ProtocolConfig dubbo = (ProtocolConfig) providerContext.getBean("dubbo");
            assertEquals(20814, dubbo.getPort().intValue());
            ProtocolConfig rmi = (ProtocolConfig) providerContext.getBean("rmi");
            assertEquals(10914, rmi.getPort().intValue());
        } finally {
            System.setProperty("dubbo.protocol.dubbo.port", "");
            System.setProperty("dubbo.protocol.rmi.port", "");
            providerContext.stop();
            providerContext.close();
        }
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSystemPropertyOverrideXmlDefault() throws Exception {
        System.setProperty("dubbo.application.name", "sysover");
        System.setProperty("dubbo.application.owner", "sysowner");
        System.setProperty("dubbo.registry.address", "N/A");
        System.setProperty("dubbo.protocol.name", "dubbo");
        System.setProperty("dubbo.protocol.port", "20819");
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/system-properties-override-default.xml");
        providerContext.start();
        try {
            ServiceConfig<DemoService> service = (ServiceConfig<DemoService>) providerContext.getBean("demoServiceConfig");
            assertEquals("sysover", service.getApplication().getName());
            assertEquals("sysowner", service.getApplication().getOwner());
            assertEquals("N/A", service.getRegistry().getAddress());
            assertEquals("dubbo", service.getProtocol().getName());
            assertEquals(20819, service.getProtocol().getPort().intValue());
        } finally {
            System.setProperty("dubbo.application.name", "");
            System.setProperty("dubbo.application.owner", "");
            System.setProperty("dubbo.registry.address", "");
            System.setProperty("dubbo.protocol.name", "");
            System.setProperty("dubbo.protocol.port", "");
            providerContext.stop();
            providerContext.close();
        }
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testSystemPropertyOverrideXml() throws Exception {
        System.setProperty("dubbo.application.name", "sysover");
        System.setProperty("dubbo.application.owner", "sysowner");
        System.setProperty("dubbo.registry.address", "N/A");
        System.setProperty("dubbo.protocol.name", "dubbo");
        System.setProperty("dubbo.protocol.port", "20819");
        System.setProperty("dubbo.service.register", "false");
        ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/system-properties-override.xml");
        providerContext.start();
        try {
            ServiceConfig<DemoService> service = (ServiceConfig<DemoService>) providerContext.getBean("demoServiceConfig");
            URL url = service.getExportedUrls().get(0);
            assertEquals("sysover", url.getParameter("application"));
            assertEquals("sysowner", url.getParameter("owner"));
            assertEquals("dubbo", url.getProtocol());
            assertEquals(20819, url.getPort());
            String register = url.getParameter("register");
            assertTrue(register != null && !"".equals(register));
            assertEquals(false, Boolean.valueOf(register));
        } finally {
            System.setProperty("dubbo.application.name", "");
            System.setProperty("dubbo.application.owner", "");
            System.setProperty("dubbo.registry.address", "");
            System.setProperty("dubbo.protocol.name", "");
            System.setProperty("dubbo.protocol.port", "");
            System.setProperty("dubbo.service.register", "");
            providerContext.stop();
            providerContext.close();
        }
    }

    @Test
    public void testSystemPropertyOverrideReferenceConfig() throws Exception {
        System.setProperty("dubbo.reference.retries", "5");

        try {
            ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
            service.setInterface(DemoService.class);
            service.setRef(new DemoServiceImpl());
            ProtocolConfig protocolConfig = new ProtocolConfig("injvm");

            ReferenceConfig<DemoService> reference = new ReferenceConfig<DemoService>();
            reference.setInterface(DemoService.class);
            reference.setInjvm(true);
            reference.setRetries(2);

            DubboBootstrap.getInstance()
                    .application(new ApplicationConfig("testSystemPropertyOverrideReferenceConfig"))
                    .registry(new RegistryConfig(RegistryConfig.NO_AVAILABLE))
                    .protocol(protocolConfig)
                    .service(service)
                    .reference(reference)
                    .start();
            assertEquals(Integer.valueOf(5), reference.getRetries());
        } finally {
            System.setProperty("dubbo.reference.retries", "");
        }
    }

    @Test
    public void testSystemPropertyOverrideApiDefault() throws Exception {
        System.setProperty("dubbo.application.name", "sysover");
        System.setProperty("dubbo.application.owner", "sysowner");
        System.setProperty("dubbo.registry.address", "N/A");
        System.setProperty("dubbo.protocol.name", "dubbo");
        System.setProperty("dubbo.protocol.port", "20834");
        try {
            ServiceConfig<DemoService> serviceConfig = new ServiceConfig<DemoService>();
            serviceConfig.setInterface(DemoService.class);
            serviceConfig.setRef(new DemoServiceImpl());

            DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                    .service(serviceConfig)
                    .start();
            try {
                assertEquals("sysover", serviceConfig.getApplication().getName());
                assertEquals("sysowner", serviceConfig.getApplication().getOwner());
                assertEquals("N/A", serviceConfig.getRegistry().getAddress());
                assertEquals("dubbo", serviceConfig.getProtocol().getName());
                assertEquals(20834, serviceConfig.getProtocol().getPort().intValue());
            } finally {
                bootstrap.stop();
            }
        } finally {
            System.setProperty("dubbo.application.name", "");
            System.setProperty("dubbo.application.owner", "");
            System.setProperty("dubbo.registry.address", "");
            System.setProperty("dubbo.protocol.name", "");
            System.setProperty("dubbo.protocol.port", "");
        }
    }

    @Test
    public void testSystemPropertyOverrideApi() throws Exception {
        System.setProperty("dubbo.application.name", "sysover");
        System.setProperty("dubbo.application.owner", "sysowner");
        System.setProperty("dubbo.registry.address", "N/A");
        System.setProperty("dubbo.protocol.name", "dubbo");
        System.setProperty("dubbo.protocol.port", "20834");
        try {
            ApplicationConfig application = new ApplicationConfig();
            application.setName("aaa");

            RegistryConfig registry = new RegistryConfig();
            registry.setAddress("127.0.0.1");

            ProtocolConfig protocol = new ProtocolConfig();
            protocol.setName("rmi");
            protocol.setPort(1099);

            ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
            service.setInterface(DemoService.class);
            service.setRef(new DemoServiceImpl());
            service.setApplication(application);
            service.setRegistry(registry);
            service.setProtocol(protocol);

            DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                    .application(application)
                    .registry(registry)
                    .protocol(protocol)
                    .service(service)
                    .start();

            try {
                URL url = service.getExportedUrls().get(0);
                assertEquals("sysover", url.getParameter("application"));
                assertEquals("sysowner", url.getParameter("owner"));
                assertEquals("dubbo", url.getProtocol());
                assertEquals(20834, url.getPort());
            } finally {
                bootstrap.stop();
            }
        } finally {
            System.setProperty("dubbo.application.name", "");
            System.setProperty("dubbo.application.owner", "");
            System.setProperty("dubbo.registry.address", "");
            System.setProperty("dubbo.protocol.name", "");
            System.setProperty("dubbo.protocol.port", "");
        }
    }

    @Test
    public void testSystemPropertyOverrideProperties() throws Exception {
        String portString = System.getProperty("dubbo.protocol.port");
        System.clearProperty("dubbo.protocol.port");
        try {
            int port = 1234;
            System.setProperty("dubbo.protocol.port", String.valueOf(port));
            ApplicationConfig application = new ApplicationConfig();
            application.setName("aaa");

            RegistryConfig registry = new RegistryConfig();
            registry.setAddress("N/A");

            ProtocolConfig protocol = new ProtocolConfig();
            protocol.setName("rmi");

            ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
            service.setInterface(DemoService.class);
            service.setRef(new DemoServiceImpl());
            service.setApplication(application);
            service.setRegistry(registry);
            service.setProtocol(protocol);

            DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                    .application(application)
                    .registry(registry)
                    .protocol(protocol)
                    .service(service)
                    .start();

            try {
                URL url = service.getExportedUrls().get(0);
                // from api
                assertEquals("aaa", url.getParameter("application"));
                // from dubbo-binder.properties
                assertEquals("world", url.getParameter("owner"));
                // from system property
                assertEquals(1234, url.getPort());
            } finally {
                bootstrap.stop();
            }
        } finally {
            if (portString != null) {
                System.setProperty("dubbo.protocol.port", portString);
            }
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testCustomizeParameter() throws Exception {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/customize-parameter.xml");
        context.start();
        ServiceBean<DemoService> serviceBean = (ServiceBean<DemoService>) context.getBean("demoServiceExport");
        URL url = (URL) serviceBean.getExportedUrls().get(0);
        assertEquals("protocol-paramA", url.getParameter("protocol.paramA"));
        assertEquals("service-paramA", url.getParameter("service.paramA"));
    }

    @Test
    public void testPath() throws Exception {
        ServiceConfig<DemoService> service = new ServiceConfig<DemoService>();
        service.setPath("a/b$c");
        try {
            service.setPath("a?b");
            fail();
        } catch (IllegalStateException e) {
            assertTrue(e.getMessage().contains(""));
        }
    }

    @Test
    public void testAnnotation() {
        SimpleRegistryService registryService = new SimpleRegistryService();
        Exporter<RegistryService> exporter = SimpleRegistryExporter.export(4548, registryService);
        try {
            ClassPathXmlApplicationContext providerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/annotation-provider.xml");
            providerContext.start();
            try {
                ClassPathXmlApplicationContext consumerContext = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/annotation-consumer.xml");
                consumerContext.start();
                try {
                    AnnotationAction annotationAction = (AnnotationAction) consumerContext.getBean("annotationAction");
                    String hello = annotationAction.doSayName("hello");
                    assertEquals("annotation:hello", hello);
                } finally {
                    consumerContext.stop();
                    consumerContext.close();
                }
            } finally {
                providerContext.stop();
                providerContext.close();
            }
        } finally {
            exporter.unexport();
        }
    }

    @Test
    public void testDubboProtocolPortOverride() throws Exception {
        String dubboPort = System.getProperty("dubbo.protocol.dubbo.port");
        int port = 55555;
        System.setProperty("dubbo.protocol.dubbo.port", String.valueOf(port));
        ServiceConfig<DemoService> service = null;
        DubboBootstrap bootstrap = null;
        try {
            ApplicationConfig application = new ApplicationConfig();
            application.setName("dubbo-protocol-port-override");

            RegistryConfig registry = new RegistryConfig();
            registry.setAddress("N/A");

            ProtocolConfig protocol = new ProtocolConfig();

            service = new ServiceConfig<DemoService>();
            service.setInterface(DemoService.class);
            service.setRef(new DemoServiceImpl());
            service.setApplication(application);
            service.setRegistry(registry);
            service.setProtocol(protocol);


            DubboBootstrap.getInstance()
                    .application(application)
                    .registry(registry)
                    .protocol(protocol)
                    .service(service)
                    .start();

            Assertions.assertEquals(port, service.getExportedUrls().get(0).getPort());
        } finally {
            if (StringUtils.isNotEmpty(dubboPort)) {
                System.setProperty("dubbo.protocol.dubbo.port", dubboPort);
            }
            if (bootstrap != null) {
                bootstrap.stop();
            }
        }
    }

    @Test
    public void testProtocolRandomPort() throws Exception {
        ServiceConfig<DemoService> demoService = null;
        ServiceConfig<HelloService> helloService = null;

        ApplicationConfig application = new ApplicationConfig();
        application.setName("test-protocol-random-port");

        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("N/A");

        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        protocol.setPort(-1);

        demoService = new ServiceConfig<DemoService>();
        demoService.setInterface(DemoService.class);
        demoService.setRef(new DemoServiceImpl());
        demoService.setApplication(application);
        demoService.setRegistry(registry);
        demoService.setProtocol(protocol);

        helloService = new ServiceConfig<HelloService>();
        helloService.setInterface(HelloService.class);
        helloService.setRef(new HelloServiceImpl());
        helloService.setApplication(application);
        helloService.setRegistry(registry);
        helloService.setProtocol(protocol);

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(application)
                .registry(registry)
                .protocol(protocol)
                .service(demoService)
                .service(helloService);
        try {
            bootstrap.start();

            Assertions.assertEquals(demoService.getExportedUrls().get(0).getPort(),
                    helloService.getExportedUrls().get(0).getPort());
        } finally {
            bootstrap.stop();
        }
    }

    @Test
    public void testReferGenericExport() throws Exception {
        RegistryConfig rc = new RegistryConfig();
        rc.setAddress(RegistryConfig.NO_AVAILABLE);

        ServiceConfig<GenericService> sc = new ServiceConfig<GenericService>();
        sc.setRegistry(rc);
        sc.setInterface(DemoService.class.getName());
        sc.setRef((method, parameterTypes, args) -> null);

        ReferenceConfig<DemoService> ref = new ReferenceConfig<DemoService>();
        ref.setRegistry(rc);
        ref.setInterface(DemoService.class.getName());

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(new ApplicationConfig("test-refer-generic-export"))
                .service(sc)
                .reference(ref);
        try {
            bootstrap.start();
            Assertions.fail();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bootstrap.stop();
        }
    }

    @Test
    public void testGenericServiceConfig() throws Exception {
        ServiceConfig<GenericService> service = new ServiceConfig<GenericService>();
        service.setRegistry(new RegistryConfig("mock://localhost"));
        service.setInterface(DemoService.class.getName());
        service.setGeneric(GENERIC_SERIALIZATION_BEAN);
        service.setRef((method, parameterTypes, args) -> null);

        DubboBootstrap bootstrap = DubboBootstrap.getInstance()
                .application(new ApplicationConfig("test"))
                .service(service)
                .start();
        try {
            Collection<Registry> collection = MockRegistryFactory.getCachedRegistry();
            MockRegistry registry = (MockRegistry) collection.iterator().next();
            URL url = registry.getRegistered().get(0);
            Assertions.assertEquals(GENERIC_SERIALIZATION_BEAN, url.getParameter(GENERIC_KEY));
        } finally {
            MockRegistryFactory.cleanCachedRegistry();
            bootstrap.stop();
        }
    }

    @Test
    public void testGenericServiceConfigThroughSpring() throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(ConfigTest.class.getPackage().getName().replace('.', '/') + "/generic-export.xml");
        try {
            ctx.start();
            ServiceConfig serviceConfig = (ServiceConfig) ctx.getBean("dubboDemoService");
            URL url = (URL) serviceConfig.getExportedUrls().get(0);
            Assertions.assertEquals(GENERIC_SERIALIZATION_BEAN, url.getParameter(GENERIC_KEY));
        } finally {
            ctx.destroy();
        }
    }
}