diff --git a/.gitignore b/.gitignore index bfaf32ca9..3e2545637 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ logs .m2 sofa-boot-gradle-plugin/build/ .gradle -sofa-boot-gradle-plugin/out/ \ No newline at end of file +sofa-boot-gradle-plugin/out/ diff --git a/.travis.yml b/.travis.yml index 59d00909d..7424eaf55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ jdk: - oraclejdk8 install: - - mvn clean install -DskipTests -B -V - mvn test diff --git a/README.md b/README.md index a059e8f7c..c6ddeba0c 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,7 @@ SOFA 的第一个版本是阿玺创造的,感谢阿玺给 SOFA 打下了坚实 ## 七、文档 请参考 [SOFABoot 官方文档](http://www.sofastack.tech/sofa-boot/docs/Home)。 + +## 八、开源许可 + +SOFABoot 基于 Apache License 2.0 协议,SOFABoot 依赖了一些三方组件,它们的开源协议参见 [依赖组件版权说明](https://www.sofastack.tech/sofa-boot/docs/NOTICE) \ No newline at end of file diff --git a/infra-sofa-boot-starter/pom.xml b/infra-sofa-boot-starter/pom.xml index e391cf6b4..f4c914a43 100644 --- a/infra-sofa-boot-starter/pom.xml +++ b/infra-sofa-boot-starter/pom.xml @@ -48,12 +48,6 @@ org.springframework spring-core - - - spring-jcl - org.springframework - - @@ -155,11 +149,6 @@ log4j-core test - - org.slf4j - jcl-over-slf4j - test - org.springframework.cloud spring-cloud-context diff --git a/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/config/spring/namespace/handler/SofaBootNamespaceHandler.java b/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/config/spring/namespace/handler/SofaBootNamespaceHandler.java index e36e215da..ff939477b 100644 --- a/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/config/spring/namespace/handler/SofaBootNamespaceHandler.java +++ b/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/config/spring/namespace/handler/SofaBootNamespaceHandler.java @@ -19,6 +19,7 @@ import com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport; import com.alipay.sofa.infra.log.InfraHealthCheckLoggerFactory; import org.slf4j.Logger; +import org.springframework.beans.factory.xml.BeanDefinitionDecorator; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; @@ -29,6 +30,7 @@ * SofaBootNamespaceHandler * * @author yangguanchao + * @author qilong.zql * @since 2018/04/08 */ public class SofaBootNamespaceHandler extends NamespaceHandlerSupport { @@ -38,19 +40,21 @@ public class SofaBootNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { - ServiceLoader serviceLoaderSofaBoot = ServiceLoader - .load(SofaBootTagNameSupport.class); + ServiceLoader serviceLoaderSofaBoot = ServiceLoader.load(SofaBootTagNameSupport.class); serviceLoaderSofaBoot.forEach(this::registerTagParser); } private void registerTagParser(SofaBootTagNameSupport tagNameSupport) { - if (!(tagNameSupport instanceof BeanDefinitionParser)) { - logger.error("{} tag name supported [{}] parser are not instance of {}.", - tagNameSupport.getClass(), tagNameSupport.supportTagName(), - BeanDefinitionParser.class); - return; + if (tagNameSupport instanceof BeanDefinitionParser) { + registerBeanDefinitionParser(tagNameSupport.supportTagName(), + (BeanDefinitionParser) tagNameSupport); + } else if (tagNameSupport instanceof BeanDefinitionDecorator) { + registerBeanDefinitionDecoratorForAttribute(tagNameSupport.supportTagName(), + (BeanDefinitionDecorator) tagNameSupport); + } else { + logger.error(tagNameSupport.getClass() + " tag name supported [" + + tagNameSupport.supportTagName() + "] parser are not instance of " + + BeanDefinitionParser.class + "or " + BeanDefinitionDecorator.class); } - String tagName = tagNameSupport.supportTagName(); - registerBeanDefinitionParser(tagName, (BeanDefinitionParser) tagNameSupport); } } diff --git a/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/constants/SofaBootInfraConstants.java b/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/constants/SofaBootInfraConstants.java index c697bb0a1..3c57b7b44 100644 --- a/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/constants/SofaBootInfraConstants.java +++ b/infra-sofa-boot-starter/src/main/java/com/alipay/sofa/infra/constants/SofaBootInfraConstants.java @@ -50,4 +50,26 @@ public class SofaBootInfraConstants { */ public static final String ENDPOINTS_WEB_EXPOSURE_INCLUDE_CONFIG = "management.endpoints.web.exposure.include"; public static final String SOFA_DEFAULT_ENDPOINTS_WEB_EXPOSURE_VALUE = "info, health, versions, readiness"; + + /** + * root application context name + */ + public static final String ROOT_APPLICATION_CONTEXT = "RootApplicationContext"; + + /** + * sofa configuration prefix + */ + public static final String PREFIX = "com.alipay.sofa.boot"; + + /** + * Thread Pool Core Size to execute async bean initialization + */ + public static final String ASYNC_INIT_BEAN_CORE_SIZE = PREFIX + + ".asyncInitBeanCoreSize"; + + /** + * Thread Pool Max Size to execute async bean initialization + */ + public static final String ASYNC_INIT_BEAN_MAX_SIZE = PREFIX + + ".asyncInitBeanMaxSize"; } diff --git a/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd b/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd index 8803fa7ca..332a2697d 100644 --- a/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd +++ b/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd @@ -33,6 +33,11 @@ + + + + + @@ -130,6 +135,7 @@ + @@ -142,6 +148,7 @@ + @@ -243,6 +250,7 @@ + @@ -255,6 +263,7 @@ + @@ -367,6 +376,7 @@ + @@ -379,6 +389,7 @@ + @@ -499,6 +510,7 @@ + @@ -511,6 +523,7 @@ + @@ -593,4 +606,7 @@ + + + diff --git a/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd b/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd index e533e0e4e..bbde08b87 100644 --- a/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd +++ b/infra-sofa-boot-starter/src/main/resources/META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd @@ -20,6 +20,7 @@ @@ -30,4 +31,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/infra-sofa-boot-starter/src/test/java/com/alipay/sofa/infra/config/spring/XsdValidationTest.java b/infra-sofa-boot-starter/src/test/java/com/alipay/sofa/infra/config/spring/XsdValidationTest.java new file mode 100644 index 000000000..7c5aab7aa --- /dev/null +++ b/infra-sofa-boot-starter/src/test/java/com/alipay/sofa/infra/config/spring/XsdValidationTest.java @@ -0,0 +1,83 @@ +/* + * 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 com.alipay.sofa.infra.config.spring; + +import com.sun.org.apache.xml.internal.utils.DefaultErrorHandler; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.xml.DefaultDocumentLoader; +import org.springframework.beans.factory.xml.DelegatingEntityResolver; +import org.springframework.beans.factory.xml.DocumentLoader; +import org.springframework.util.xml.XmlValidationModeDetector; +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; + +/** + * XML validation with rpc.xsd + * + * @author ScienJus + */ +public class XsdValidationTest { + + @Test + public void testSofaParameter() throws Exception { + loadXml("config/spring/test_sofa_parameter.xml"); + } + + @Test + public void testSofaParameterMissingKey() throws Exception { + try { + loadXml("config/spring/test_sofa_parameter_missing_key.xml"); + Assert.fail(); + } catch (SAXParseException e) { + // org.xml.sax.SAXParseException; lineNumber: 10; columnNumber: 52; + // cvc-complex-type.4: Attribute 'key' must appear on element 'sofa:parameter'. + assertSaxException(10, 52, "cvc-complex-type.4", e); + } + } + + @Test + public void testSofaParameterOutsideBinding() throws Exception { + try { + loadXml("config/spring/test_sofa_parameter_outside_binding.xml"); + Assert.fail(); + } catch (SAXParseException e) { + // org.xml.sax.SAXParseException; lineNumber: 11; columnNumber: 65; + // cvc-complex-type.2.4.a: Invalid content was found starting with element 'sofa:parameter'. + // One of '{"http://sofastack.io/schema/sofaboot":binding.jvm, + // "http://sofastack.io/schema/sofaboot":binding.rest, + // "http://sofastack.io/schema/sofaboot":binding.dubbo, + // "http://sofastack.io/schema/sofaboot":binding.h2c}' is expected. + assertSaxException(11, 65, "cvc-complex-type.2.4.a", e); + } + } + + private void assertSaxException(int expectedLineNumber, int expectedColumnNumber, + String expectedErrorCode, SAXParseException e) { + Assert.assertEquals(expectedLineNumber, e.getLineNumber()); + Assert.assertEquals(expectedColumnNumber, e.getColumnNumber()); + Assert.assertEquals(expectedErrorCode, e.getMessage().split(":")[0]); + } + + private void loadXml(String xml) throws Exception { + DocumentLoader documentLoader = new DefaultDocumentLoader(); + documentLoader.loadDocument(new InputSource(this.getClass().getClassLoader() + .getResourceAsStream(xml)), new DelegatingEntityResolver(this.getClass() + .getClassLoader()), new DefaultErrorHandler(), + XmlValidationModeDetector.VALIDATION_XSD, true); + } +} diff --git a/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter.xml b/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter.xml new file mode 100644 index 000000000..b26a8be41 --- /dev/null +++ b/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter_missing_key.xml b/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter_missing_key.xml new file mode 100644 index 000000000..e168f5af2 --- /dev/null +++ b/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter_missing_key.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter_outside_binding.xml b/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter_outside_binding.xml new file mode 100644 index 000000000..9bf08dc8f --- /dev/null +++ b/infra-sofa-boot-starter/src/test/resources/config/spring/test_sofa_parameter_outside_binding.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/deployment/impl/AbstractDeploymentDescriptor.java b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/deployment/impl/AbstractDeploymentDescriptor.java index 7f82afc17..1f04889db 100644 --- a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/deployment/impl/AbstractDeploymentDescriptor.java +++ b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/deployment/impl/AbstractDeploymentDescriptor.java @@ -200,4 +200,21 @@ private List getFormattedModuleInfo(String key) { } protected abstract void loadSpringXMLs(); + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AbstractDeploymentDescriptor)) { + return false; + } + AbstractDeploymentDescriptor that = (AbstractDeploymentDescriptor) o; + return Objects.equals(this.getModuleName(), that.getModuleName()); + } + + @Override + public int hashCode() { + return Objects.hash(getModuleName()); + } } diff --git a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/loader/DynamicSpringContextLoader.java b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/loader/DynamicSpringContextLoader.java index 64840ac34..397496140 100644 --- a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/loader/DynamicSpringContextLoader.java +++ b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/loader/DynamicSpringContextLoader.java @@ -61,7 +61,7 @@ public void loadSpringContext(DeploymentDescriptor deployment, SofaModuleProperties.class); BeanLoadCostBeanFactory beanFactory = new BeanLoadCostBeanFactory( - sofaModuleProperties.getBeanLoadCost()); + sofaModuleProperties.getBeanLoadCost(), deployment.getModuleName()); beanFactory .setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); GenericApplicationContext ctx = sofaModuleProperties.isPublishEventToParent() ? new GenericApplicationContext( @@ -79,8 +79,7 @@ public void loadSpringContext(DeploymentDescriptor deployment, CachedIntrospectionResults.acceptClassLoader(moduleClassLoader); // set allowBeanDefinitionOverriding - ctx.setAllowBeanDefinitionOverriding(rootApplicationContext.getBean( - SofaModuleProperties.class).isAllowBeanDefinitionOverriding()); + ctx.setAllowBeanDefinitionOverriding(sofaModuleProperties.isAllowBeanDefinitionOverriding()); ctx.getBeanFactory().setBeanClassLoader(moduleClassLoader); ctx.getBeanFactory().addPropertyEditorRegistrar(new PropertyEditorRegistrar() { diff --git a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/spring/factory/BeanLoadCostBeanFactory.java b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/spring/factory/BeanLoadCostBeanFactory.java index 4a7b82d73..9f3852363 100644 --- a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/spring/factory/BeanLoadCostBeanFactory.java +++ b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/spring/factory/BeanLoadCostBeanFactory.java @@ -36,8 +36,11 @@ public class BeanLoadCostBeanFactory extends DefaultListableBeanFactory { private long beanLoadCost = DEFAULT_BEAN_LOAD_COST; - public BeanLoadCostBeanFactory(long beanCost) { + private String moduleName; + + public BeanLoadCostBeanFactory(long beanCost, String moduleName) { this.beanLoadCost = beanCost; + this.moduleName = moduleName; } protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) @@ -70,6 +73,10 @@ public List getBeanLoadList() { return beanCostList; } + public String getModuleName() { + return moduleName; + } + public class BeanNode { public String beanClassName; public long costTime; diff --git a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/stage/SpringContextInstallStage.java b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/stage/SpringContextInstallStage.java index b0022ccf4..ad1c61859 100644 --- a/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/stage/SpringContextInstallStage.java +++ b/isle-sofa-boot-starter/src/main/java/com/alipay/sofa/isle/stage/SpringContextInstallStage.java @@ -155,7 +155,7 @@ private void refreshSpringContext(ApplicationRuntimeModel application) { ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); try { for (DeploymentDescriptor deployment : application.getResolvedDeployments()) { - if (deployment.isSpringPowered()) { + if (deployment.isSpringPowered() && !application.getFailed().contains(deployment)) { Thread.currentThread().setContextClassLoader(deployment.getClassLoader()); doRefreshSpringContext(deployment, application); } @@ -236,7 +236,8 @@ public void run() { Thread.currentThread().setName( "sofa-module-start-" + deployment.getModuleName()); Thread.currentThread().setContextClassLoader(deployment.getClassLoader()); - if (deployment.isSpringPowered()) { + if (deployment.isSpringPowered() + && !application.getFailed().contains(deployment)) { doRefreshSpringContext(deployment, application); } DependencyTree.Entry entry = application diff --git a/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/integration/FailModuleTest.java b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/integration/FailModuleTest.java new file mode 100644 index 000000000..8d8d6d460 --- /dev/null +++ b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/integration/FailModuleTest.java @@ -0,0 +1,76 @@ +/* + * 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 com.alipay.sofa.isle.integration; + +import com.alipay.sofa.healthcheck.core.HealthChecker; +import com.alipay.sofa.isle.ApplicationRuntimeModel; +import com.alipay.sofa.isle.constants.SofaModuleFrameworkConstants; +import com.alipay.sofa.isle.deployment.DeploymentDescriptor; +import com.alipay.sofa.isle.util.AddCustomJar; +import com.alipay.sofa.isle.util.SeparateClassLoaderTestRunner; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.BeansException; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.test.annotation.DirtiesContext; + +import static org.junit.Assert.assertEquals; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@RunWith(SeparateClassLoaderTestRunner.class) +@SpringBootTest(classes = SofaBootTestApplication.class) +@AddCustomJar({ "dev-module-0.1.0.jar", "fail-module-0.1.0.jar" }) +@DirtiesContext +public class FailModuleTest implements ApplicationContextAware { + private ApplicationContext applicationContext; + + @Test + public void test() { + ApplicationRuntimeModel applicationRuntimeModel = (ApplicationRuntimeModel) applicationContext + .getBean(SofaModuleFrameworkConstants.APPLICATION); + + // contains three Deployments + assertEquals(3, applicationRuntimeModel.getAllDeployments().size()); + assertEquals(2, applicationRuntimeModel.getInstalled().size()); + assertEquals(1, applicationRuntimeModel.getFailed().size()); + + // check module not in installed list + DeploymentDescriptor failModule = applicationRuntimeModel.getFailed().get(0); + Assert.assertEquals("com.alipay.sofa.fail", failModule.getModuleName()); + Assert.assertFalse(applicationRuntimeModel.getInstalled().contains(failModule)); + } + + @Test + public void testHealthChecker() { + Assert.assertNotNull(applicationContext.getBean("sofaModuleHealthChecker")); + HealthChecker healthChecker = (HealthChecker) applicationContext + .getBean("sofaModuleHealthChecker"); + Assert.assertEquals(Status.DOWN, healthChecker.isHealthy().getStatus()); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} \ No newline at end of file diff --git a/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/integration/FailModuleWithParallelTest.java b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/integration/FailModuleWithParallelTest.java new file mode 100644 index 000000000..29752b5fc --- /dev/null +++ b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/integration/FailModuleWithParallelTest.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 com.alipay.sofa.isle.integration; + +import org.springframework.context.ApplicationContextAware; +import org.springframework.test.context.TestPropertySource; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@TestPropertySource(properties = "com.alipay.sofa.boot.module-start-up-parallel=true") +public class FailModuleWithParallelTest extends FailModuleTest implements ApplicationContextAware { + +} \ No newline at end of file diff --git a/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/scan/FailBean.java b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/scan/FailBean.java new file mode 100644 index 000000000..b51bd9156 --- /dev/null +++ b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/scan/FailBean.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 com.alipay.sofa.isle.scan; + +import org.springframework.beans.factory.InitializingBean; + +/** + * Test For Fail Module + * @author ruoshan + * @since 2.6.0 + */ +public class FailBean implements InitializingBean { + + @Override + public void afterPropertiesSet() throws Exception { + throw new RuntimeException("fail bean test!!!"); + } +} \ No newline at end of file diff --git a/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/spring/parser/AsyncInitBeanDefinitionDecoratorTest.java b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/spring/parser/AsyncInitBeanDefinitionDecoratorTest.java new file mode 100644 index 000000000..6f830f70c --- /dev/null +++ b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/spring/parser/AsyncInitBeanDefinitionDecoratorTest.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 com.alipay.sofa.isle.spring.parser; + +import com.alipay.sofa.infra.constants.SofaBootInfraConstants; +import com.alipay.sofa.isle.spring.factory.BeanLoadCostBeanFactory; +import com.alipay.sofa.runtime.spring.parser.AsyncInitBeanDefinitionDecorator; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +/** + * @author qilong.zql + * @author ruoshan + * @since 2.6.0 + */ +public class AsyncInitBeanDefinitionDecoratorTest { + + @Test + public void testIsleModule() { + String moduleName = "testModule"; + BeanLoadCostBeanFactory beanFactory = new BeanLoadCostBeanFactory(10, moduleName); + Assert.assertTrue(AsyncInitBeanDefinitionDecorator.isBeanLoadCostBeanFactory(beanFactory + .getClass())); + Assert.assertEquals(moduleName, + AsyncInitBeanDefinitionDecorator.getModuleNameFromBeanFactory(beanFactory)); + } + + @Test + public void testNoIsleModule() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + Assert.assertFalse(AsyncInitBeanDefinitionDecorator.isBeanLoadCostBeanFactory(beanFactory + .getClass())); + Assert.assertEquals(SofaBootInfraConstants.ROOT_APPLICATION_CONTEXT, + AsyncInitBeanDefinitionDecorator.getModuleNameFromBeanFactory(beanFactory)); + } + +} diff --git a/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/util/AddCustomJar.java b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/util/AddCustomJar.java new file mode 100644 index 000000000..c8a77e5ca --- /dev/null +++ b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/util/AddCustomJar.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 com.alipay.sofa.isle.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AddCustomJar { + + String[] value(); + +} \ No newline at end of file diff --git a/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/util/SeparateClassLoaderTestRunner.java b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/util/SeparateClassLoaderTestRunner.java new file mode 100644 index 000000000..581c5b6b5 --- /dev/null +++ b/isle-sofa-boot-starter/src/test/java/com/alipay/sofa/isle/util/SeparateClassLoaderTestRunner.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 com.alipay.sofa.isle.util; + +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.InitializationError; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.URLClassLoader; + +/** + * + * @author xuanbei + * @author ruoshan + * @since 2.6.0 + */ +public class SeparateClassLoaderTestRunner extends SpringJUnit4ClassRunner { + + private final SeparateClassLoader separateClassloader = new SeparateClassLoader(); + + private Method runMethod; + private Object runnerObject; + + public SeparateClassLoaderTestRunner(Class clazz) throws InitializationError { + super(clazz); + try { + AddCustomJar customJar = getAddCustomJarAnnotationRecursively(clazz); + if (customJar != null) { + for (String jar : customJar.value()) { + separateClassloader.addJar(jar); + } + } + Class springJUnit4ClassRunnerClass = separateClassloader + .loadClass(SpringJUnit4ClassRunner.class.getName()); + Constructor constructor = springJUnit4ClassRunnerClass.getConstructor(Class.class); + runnerObject = constructor.newInstance(separateClassloader.loadClass(clazz.getName())); + runMethod = springJUnit4ClassRunnerClass.getMethod("run", RunNotifier.class); + + } catch (Throwable e) { + throw new InitializationError(e); + } + } + + @Override + public void run(RunNotifier notifier) { + ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(separateClassloader); + runMethod.invoke(runnerObject, notifier); + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + Thread.currentThread().setContextClassLoader(oldContextClassLoader); + } + } + + public static AddCustomJar getAddCustomJarAnnotationRecursively(Class klass) { + AddCustomJar addCustomJar = klass.getAnnotation(AddCustomJar.class); + + if (addCustomJar != null || klass == Object.class) { + return addCustomJar; + } + + return getAddCustomJarAnnotationRecursively(klass.getSuperclass()); + } + + public static class SeparateClassLoader extends URLClassLoader { + public SeparateClassLoader() { + super(((URLClassLoader) getSystemClassLoader()).getURLs(), null); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (name.startsWith("org.junit") || name.startsWith("java") + || name.startsWith("org.apache.logging")) { + return getSystemClassLoader().loadClass(name); + } + + return super.loadClass(name); + } + + void addJar(String jar) { + super.addURL(SeparateClassLoaderTestRunner.class.getClassLoader().getResource(jar)); + } + } +} \ No newline at end of file diff --git a/isle-sofa-boot-starter/src/test/resources/config/application.properties b/isle-sofa-boot-starter/src/test/resources/config/application.properties index 0616b87cc..649cddc2a 100644 --- a/isle-sofa-boot-starter/src/test/resources/config/application.properties +++ b/isle-sofa-boot-starter/src/test/resources/config/application.properties @@ -3,4 +3,6 @@ spring.application.name=isle-test com.alipay.sofa.boot.active-profiles=dev com.alipay.sofa.boot.bean-load-cost=0 com.alipay.sofa.boot.allow-bean-definition-overriding=false -com.alipay.sofa.boot.module-start-up-parallel=false \ No newline at end of file +com.alipay.sofa.boot.module-start-up-parallel=false + +logging.path=./logs \ No newline at end of file diff --git a/isle-sofa-boot-starter/src/test/resources/fail-module-0.1.0.jar b/isle-sofa-boot-starter/src/test/resources/fail-module-0.1.0.jar new file mode 100644 index 000000000..13bd0a139 Binary files /dev/null and b/isle-sofa-boot-starter/src/test/resources/fail-module-0.1.0.jar differ diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Context.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Context.java new file mode 100644 index 000000000..6627703af --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Context.java @@ -0,0 +1,67 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import java.net.URL; +import java.util.ArrayList; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class Context extends ArrayList { + + private static final long serialVersionUID = 1L; + + public Class loadClass(String className) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass(className); + } + + public URL getResource(String name) { + return Thread.currentThread().getContextClassLoader().getResource(name); + } + + public Object getObject() { + int size = size(); + if (size > 0) { + return get(size - 1); + } + return null; + } + + public Object getParent() { + int size = size(); + if (size > 1) { + return get(size - 2); + } + return null; + } + + public void push(Object object) { + add(object); + } + + public Object pop() { + int size = size(); + if (size > 0) { + return remove(size - 1); + } + return null; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/DOMHelper.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/DOMHelper.java new file mode 100644 index 000000000..781c1f882 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/DOMHelper.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 com.alipay.sofa.common.xmap; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.Collection; +import java.util.Map; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public final class DOMHelper { + + private DOMHelper() { + } + + /** + * Gets the value of the node at the given path + * relative to the given base element. + * For element nodes the value is the text content and for + * the attributes node the attribute value. + * + * @param base base element + * @param path path + * @return the node value or null if no such node was found + */ + public static String getNodeValue(Element base, Path path) { + Node node = getElementNode(base, path); + if (node != null) { + if (path.attribute != null) { + Node at = node.getAttributes().getNamedItem(path.attribute); + return at != null ? at.getNodeValue() : null; + } else { + return node.getTextContent(); + } + } + return null; + } + + public static void visitNodes(Context ctx, XAnnotatedList xam, Element base, Path path, + NodeVisitor visitor, Collection result) { + Node el = base; + int len = path.segments.length - 1; + for (int i = 0; i < len; i++) { + el = getElementNode(el, path.segments[i]); + if (el == null) { + return; + } + } + String name = path.segments[len]; + + if (path.attribute != null) { + visitAttributes(ctx, xam, el, name, path.attribute, visitor, result); + } else { + visitElements(ctx, xam, el, name, visitor, result); + } + } + + public static void visitAttributes(Context ctx, XAnnotatedList xam, Node base, String name, + String attrName, NodeVisitor visitor, + Collection result) { + Node p = base.getFirstChild(); + while (p != null) { + if (p.getNodeType() == Node.ELEMENT_NODE) { + if (name.equals(p.getNodeName())) { + Node at = p.getAttributes().getNamedItem(attrName); + if (at != null) { + visitor.visitNode(ctx, xam, at, result); + } + } + } + p = p.getNextSibling(); + } + } + + public static void visitElements(Context ctx, XAnnotatedList xam, Node base, String name, + NodeVisitor visitor, Collection result) { + Node p = base.getFirstChild(); + while (p != null) { + if (p.getNodeType() == Node.ELEMENT_NODE) { + if (name.equals(p.getNodeName())) { + visitor.visitNode(ctx, xam, p, result); + } + } + p = p.getNextSibling(); + } + } + + public static void visitMapNodes(Context ctx, XAnnotatedMap xam, Element base, Path path, + NodeMapVisitor visitor, Map result) { + Node el = base; + int len = path.segments.length - 1; + for (int i = 0; i < len; i++) { + el = getElementNode(el, path.segments[i]); + if (el == null) { + return; + } + } + String name = path.segments[len]; + + if (path.attribute != null) { + visitMapAttributes(ctx, xam, el, name, path.attribute, visitor, result); + } else { + visitMapElements(ctx, xam, el, name, visitor, result); + } + } + + public static void visitMapAttributes(Context ctx, XAnnotatedMap xam, Node base, String name, + String attrName, NodeMapVisitor visitor, + Map result) { + Node p = base.getFirstChild(); + while (p != null) { + if (p.getNodeType() == Node.ELEMENT_NODE) { + if (name.equals(p.getNodeName())) { + Node at = p.getAttributes().getNamedItem(attrName); + if (at != null) { + String key = getNodeValue((Element) p, xam.key); + if (key != null) { + visitor.visitNode(ctx, xam, at, key, result); + } + } + } + } + p = p.getNextSibling(); + } + } + + public static void visitMapElements(Context ctx, XAnnotatedMap xam, Node base, String name, + NodeMapVisitor visitor, Map result) { + Node p = base.getFirstChild(); + while (p != null) { + if (p.getNodeType() == Node.ELEMENT_NODE) { + if (name.equals(p.getNodeName())) { + String key = getNodeValue((Element) p, xam.key); + if (key != null) { + visitor.visitNode(ctx, xam, p, key, result); + } + } + } + p = p.getNextSibling(); + } + } + + public static Node getElementNode(Node base, String name) { + Node node = base.getFirstChild(); + while (node != null) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (name.equals(node.getNodeName())) { + return node; + } + } + node = node.getNextSibling(); + } + return null; + } + + public static Node getElementNode(Node base, Path path) { + Node el = base; + int len = path.segments.length; + for (int i = 0; i < len; i++) { + el = getElementNode(el, path.segments[i]); + if (el == null) { + return null; + } + } + return el; + } + + public abstract static class NodeVisitor { + + public abstract void visitNode(Context ctx, XAnnotatedMember xam, Node node, + Collection result); + + } + + public abstract static class NodeMapVisitor { + + public abstract void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, + Map result); + + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/DOMSerializer.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/DOMSerializer.java new file mode 100755 index 000000000..465d2e4be --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/DOMSerializer.java @@ -0,0 +1,119 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.sun.org.apache.xml.internal.serialize.OutputFormat; +import com.sun.org.apache.xml.internal.serialize.XMLSerializer; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public final class DOMSerializer { + + private static final DocumentBuilderFactory BUILDER_FACTORY = DocumentBuilderFactory + .newInstance(); + + // Default output format which is : no xml declaration, no document type, indent. + private static final OutputFormat DEFAULT_FORMAT = new OutputFormat(); + + static { + DEFAULT_FORMAT.setOmitXMLDeclaration(false); + DEFAULT_FORMAT.setIndenting(true); + DEFAULT_FORMAT.setMethod("xml"); + DEFAULT_FORMAT.setEncoding("UTF-8"); + } + + private DOMSerializer() { + } + + public static DocumentBuilderFactory getBuilderFactory() { + return BUILDER_FACTORY; + } + + public static String toString(Element element) throws IOException { + return toString(element, DEFAULT_FORMAT); + } + + public static String toString(Element element, OutputFormat format) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + write(element, format, baos); + return baos.toString(); + } + + public static String toString(DocumentFragment fragment) throws IOException { + return toString(fragment, DEFAULT_FORMAT); + } + + public static String toString(DocumentFragment fragment, OutputFormat format) + throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + write(fragment, format, baos); + return baos.toString(); + } + + public static String toString(Document doc) throws IOException { + return toString(doc, DEFAULT_FORMAT); + } + + public static String toString(Document doc, OutputFormat format) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + write(doc, format, baos); + return baos.toString(); + } + + public static void write(Element element, OutputStream out) throws IOException { + write(element, DEFAULT_FORMAT, out); + } + + public static void write(Element element, OutputFormat format, OutputStream out) + throws IOException { + XMLSerializer serializer = new XMLSerializer(out, format); + serializer.asDOMSerializer().serialize(element); + } + + public static void write(DocumentFragment fragment, OutputStream out) throws IOException { + write(fragment, DEFAULT_FORMAT, out); + } + + public static void write(DocumentFragment fragment, OutputFormat format, OutputStream out) + throws IOException { + XMLSerializer serializer = new XMLSerializer(out, format); + serializer.asDOMSerializer().serialize(fragment); + } + + public static void write(Document doc, OutputStream out) throws IOException { + write(doc, DEFAULT_FORMAT, out); + } + + public static void write(Document doc, OutputFormat format, OutputStream out) + throws IOException { + XMLSerializer serializer = new XMLSerializer(out, format); + serializer.asDOMSerializer().serialize(doc); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Path.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Path.java new file mode 100644 index 000000000..3bc745a59 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Path.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 com.alipay.sofa.common.xmap; + +import java.util.ArrayList; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class Path { + + public static final String[] EMPTY_SEGMENTS = new String[0]; + + public final String path; + public String[] segments; + public String attribute; + + public Path(String path) { + this.path = path; + parse(path); + } + + @Override + public String toString() { + return path; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Path) { + return ((Path) obj).path.equals(path); + } + return false; + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + private void parse(String path) { + ArrayList seg = new ArrayList(); + StringBuffer buf = new StringBuffer(); + char[] chars = path.toCharArray(); + boolean attr = false; + for (char c : chars) { + switch (c) { + case '/': + seg.add(buf.toString()); + buf.setLength(0); + break; + case '@': + attr = true; + seg.add(buf.toString()); + buf.setLength(0); + break; + default: + buf.append(c); + break; + } + } + if (buf.length() > 0) { + if (attr) { + attribute = buf.toString(); + } else { + seg.add(buf.toString()); + } + } + int size = seg.size(); + if (size == 1 && seg.get(0).length() == 0) { + segments = EMPTY_SEGMENTS; + } else { + segments = seg.toArray(new String[size]); + } + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/PrimitiveArrays.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/PrimitiveArrays.java new file mode 100644 index 000000000..0e91bf8bc --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/PrimitiveArrays.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 com.alipay.sofa.common.xmap; + +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public final class PrimitiveArrays { + + private PrimitiveArrays() { + } + + @SuppressWarnings({ "ObjectEquality" }) + public static Object toPrimitiveArray(Collection col, Class primitiveArrayType) { + if (primitiveArrayType == Integer.TYPE) { + return toIntArray(col); + } else if (primitiveArrayType == Long.TYPE) { + return toLongArray(col); + } else if (primitiveArrayType == Double.TYPE) { + return toDoubleArray(col); + } else if (primitiveArrayType == Float.TYPE) { + return toFloatArray(col); + } else if (primitiveArrayType == Boolean.TYPE) { + return toBooleanArray(col); + } else if (primitiveArrayType == Byte.TYPE) { + return toByteArray(col); + } else if (primitiveArrayType == Character.TYPE) { + return toCharArray(col); + } else if (primitiveArrayType == Short.TYPE) { + return toShortArray(col); + } + return null; + } + + public static int[] toIntArray(Collection col) { + int size = col.size(); + int[] ar = new int[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Integer) it.next(); + } + return ar; + } + + public static long[] toLongArray(Collection col) { + int size = col.size(); + long[] ar = new long[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Long) it.next(); + } + return ar; + } + + public static double[] toDoubleArray(Collection col) { + int size = col.size(); + double[] ar = new double[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Double) it.next(); + } + return ar; + } + + public static float[] toFloatArray(Collection col) { + int size = col.size(); + float[] ar = new float[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Float) it.next(); + } + return ar; + } + + public static boolean[] toBooleanArray(Collection col) { + int size = col.size(); + boolean[] ar = new boolean[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Boolean) it.next(); + } + return ar; + } + + public static short[] toShortArray(Collection col) { + int size = col.size(); + short[] ar = new short[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Short) it.next(); + } + return ar; + } + + public static byte[] toByteArray(Collection col) { + int size = col.size(); + byte[] ar = new byte[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Byte) it.next(); + } + return ar; + } + + public static char[] toCharArray(Collection col) { + int size = col.size(); + char[] ar = new char[size]; + Iterator it = col.iterator(); + int i = 0; + while (it.hasNext()) { + ar[i++] = (Character) it.next(); + } + return ar; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Resource.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Resource.java new file mode 100644 index 000000000..27115fd0e --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/Resource.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 com.alipay.sofa.common.xmap; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +/** + * A resource that can be retrieved using the class loader. + *

+ * This is wrapping an URL as returned by the class loader. + *

+ * The URL class cannot be used directly because it already has a factory associated to it that constructs the URL using + * its constructor. + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class Resource { + + private final URL url; + + public Resource(URL url) { + this.url = url; + } + + public Resource(Context ctx, String path) { + url = ctx.getResource(path); + } + + public URL toURL() { + return url; + } + + public URI toURI() throws URISyntaxException { + return url != null ? url.toURI() : null; + } + + public File toFile() throws URISyntaxException { + return url != null ? new File(url.toURI()) : null; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedContent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedContent.java new file mode 100755 index 000000000..c32c5017d --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedContent.java @@ -0,0 +1,80 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.common.xmap.annotation.XContent; +import com.sun.org.apache.xml.internal.serialize.OutputFormat; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.ranges.DocumentRange; +import org.w3c.dom.ranges.Range; + +import java.io.IOException; +import java.util.List; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedContent extends XAnnotatedMember { + + private static final OutputFormat DEFAULT_FORMAT = new OutputFormat(); + + static { + DEFAULT_FORMAT.setOmitXMLDeclaration(true); + DEFAULT_FORMAT.setIndenting(true); + DEFAULT_FORMAT.setMethod("xml"); + DEFAULT_FORMAT.setEncoding("UTF-8"); + } + + public XAnnotatedContent(XMap xmap, XSetter setter, XGetter getter, XContent anno) { + super(xmap, setter, getter); + this.path = new Path(anno.value()); + this.type = setter.getType(); + this.valueFactory = xmap.getValueFactory(this.type); + this.xao = xmap.register(this.type); + } + + @Override + protected Object getValue(Context ctx, Element base) throws IOException { + Element el = (Element) DOMHelper.getElementNode(base, path); + if (el == null) { + return null; + } + el.normalize(); + Node node = el.getFirstChild(); + if (node == null) { + return ""; + } + Range range = ((DocumentRange) el.getOwnerDocument()).createRange(); + range.setStartBefore(node); + range.setEndAfter(el.getLastChild()); + DocumentFragment fragment = range.cloneContents(); + boolean asDOM = setter.getType() == DocumentFragment.class; + return asDOM ? fragment : DOMSerializer.toString(fragment, DEFAULT_FORMAT); + } + + @Override + public void decode(Object instance, Node base, Document document, List filters) + throws Exception { + // do nothing + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedList.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedList.java new file mode 100644 index 000000000..0ca6bd053 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedList.java @@ -0,0 +1,233 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.common.xmap.annotation.XNodeList; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedList extends XAnnotatedMember { + + protected static final ElementVisitor elementListVisitor = new ElementVisitor(); + protected static final ElementValueVisitor elementVisitor = new ElementValueVisitor(); + protected static final AttributeValueVisitor attributeVisitor = new AttributeValueVisitor(); + + // indicates the type of the collection components + public Class componentType; + + protected XAnnotatedList(XMap xmap, XSetter setter, XGetter getter) { + super(xmap, setter, getter); + } + + public XAnnotatedList(XMap xmap, XSetter setter, XGetter getter, XNodeList anno) { + super(xmap, setter, getter); + path = new Path(anno.value()); + trim = anno.trim(); + type = anno.type(); + cdata = anno.cdata(); + componentType = anno.componentType(); + valueFactory = xmap.getValueFactory(componentType); + xao = xmap.register(componentType); + } + + @SuppressWarnings("unchecked") + @Override + protected Object getValue(Context ctx, Element base) throws Exception { + ArrayList values = new ArrayList(); + if (xao != null) { + DOMHelper.visitNodes(ctx, this, base, path, elementListVisitor, values); + } else { + if (path.attribute != null) { + // attribute list + DOMHelper.visitNodes(ctx, this, base, path, attributeVisitor, values); + } else { + // element list + DOMHelper.visitNodes(ctx, this, base, path, elementVisitor, values); + } + } + + if (type != ArrayList.class) { + if (type.isArray()) { + if (componentType.isPrimitive()) { + // primitive arrays cannot be casted to Object[] + return PrimitiveArrays.toPrimitiveArray(values, componentType); + } else { + return values + .toArray((Object[]) Array.newInstance(componentType, values.size())); + } + } else { + Collection col = (Collection) type.newInstance(); + col.addAll(values); + return col; + } + } + + return values; + } + + @SuppressWarnings("unchecked") + public void decode(Object instance, Node base, Document document, List filters) + throws Exception { + if (!isFilter(filters)) { + return; + } + + Collection col = null; + if (Collection.class.isAssignableFrom(type)) { + col = (Collection) getter.getValue(instance); + } else { + if (type.isArray()) { + col = new ArrayList(); + + Object obj = getter.getValue(instance); + + int length = Array.getLength(obj); + + for (int i = 0; i < length; i++) { + col.add(Array.get(obj, i)); + } + } else { + throw new Exception("@XNodeList " + base.getNodeName() + + " 'type' only support Collection ande Array type"); + } + } + + Node node = base; + int len = path.segments.length - 1; + for (int i = 0; i < len; i++) { + Node n = DOMHelper.getElementNode(node, path.segments[i]); + if (n == null) { + Element element = document.createElement(path.segments[i]); + node = node.appendChild(element); + } else { + node = n; + } + } + + String name = path.segments[len]; + + Node lastParentNode = node; + + for (Object object : col) { + + Element element = document.createElement(name); + node = lastParentNode.appendChild(element); + + if (xao != null) { + xao.decode(object, node, document, filters); + } else { + String value = object == null ? "" : object.toString(); + + if (path.attribute != null && path.attribute.length() > 0) { + Attr attr = document.createAttribute(path.attribute); + attr.setNodeValue(value); + + ((Element) node).setAttributeNode(attr); + } else { + if (cdata) { + CDATASection cdataSection = document.createCDATASection(value); + node.appendChild(cdataSection); + } else { + node.setTextContent(value); + } + } + } + } + + } +} + +/** + * ElementNodeVisitor + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +class ElementVisitor extends DOMHelper.NodeVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection result) { + try { + result.add(xam.xao.newInstance(ctx, (Element) node)); + } catch (Throwable e) { + SofaLogger.error("visitNode error", e); + } + } +} + +/** + * ElementValueNodeVisitor + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +class ElementValueVisitor extends DOMHelper.NodeVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection result) { + String val = node.getTextContent(); + if (xam.trim) { + val = val.trim(); + } + if (xam.valueFactory != null) { + result.add(xam.valueFactory.getValue(ctx, val)); + } else { + // TODO: log warning? + result.add(val); + } + } +} + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +class AttributeValueVisitor extends DOMHelper.NodeVisitor { + /** + * + * @param ctx + * @param xam + * @param node + * @param result + */ + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection result) { + String val = node.getNodeValue(); + if (xam.valueFactory != null) { + result.add(xam.valueFactory.getValue(ctx, val)); + } else { + // TODO: log warning? + result.add(val); + } + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedMap.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedMap.java new file mode 100644 index 000000000..f4c67d001 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedMap.java @@ -0,0 +1,207 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.common.xmap.annotation.XNodeMap; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedMap extends XAnnotatedList { + + protected static final ElementMapVisitor elementMapVisitor = new ElementMapVisitor(); + protected static final ElementValueMapVisitor elementVisitor = new ElementValueMapVisitor(); + protected static final AttributeValueMapVisitor attributeVisitor = new AttributeValueMapVisitor(); + + protected Path key; + + public XAnnotatedMap(XMap xmap, XSetter setter, XGetter getter, XNodeMap anno) { + super(xmap, setter, getter); + if (anno != null) { + path = new Path(anno.value()); + trim = anno.trim(); + key = new Path(anno.key()); + type = anno.type(); + cdata = anno.cdata(); + componentType = anno.componentType(); + valueFactory = xmap.getValueFactory(componentType); + xao = xmap.register(componentType); + } + + } + + @SuppressWarnings("unchecked") + @Override + protected Object getValue(Context ctx, Element base) throws IllegalAccessException, + InstantiationException { + Map values = (Map) type.newInstance(); + if (xao != null) { + DOMHelper.visitMapNodes(ctx, this, base, path, elementMapVisitor, values); + } else { + if (path.attribute != null) { + // attribute list + DOMHelper.visitMapNodes(ctx, this, base, path, attributeVisitor, values); + } else { + // element list + DOMHelper.visitMapNodes(ctx, this, base, path, elementVisitor, values); + } + } + + return values; + } + + @SuppressWarnings("unchecked") + @Override + public void decode(Object instance, Node base, Document document, List filters) + throws Exception { + if (!isFilter(filters)) { + return; + } + + Map values = (Map) getter.getValue(instance); + + Node node = base; + int len = path.segments.length - 1; + for (int i = 0; i < len; i++) { + Node n = DOMHelper.getElementNode(node, path.segments[i]); + if (n == null) { + Element element = document.createElement(path.segments[i]); + node = node.appendChild(element); + } else { + node = n; + } + } + + String name = path.segments[len]; + + Node lastParentNode = node; + + Set entrys = values.entrySet(); + for (Map.Entry entry : entrys) { + + Element element = document.createElement(name); + node = lastParentNode.appendChild(element); + + Object keyObj = entry.getKey(); + String keyValue = keyObj == null ? "" : keyObj.toString(); + + Object object = entry.getValue(); + + Attr attrKey = document.createAttribute(key.attribute); + attrKey.setNodeValue(keyValue); + ((Element) node).setAttributeNode(attrKey); + + if (xao != null) { + xao.decode(object, node, document, filters); + } else { + String value = object == null ? "" : object.toString(); + + if (path.attribute != null && path.attribute.length() > 0) { + Attr attrValue = document.createAttribute(path.attribute); + attrValue.setNodeValue(value); + + ((Element) node).setAttributeNode(attrValue); + } else { + if (cdata) { + CDATASection cdataSection = document.createCDATASection(value); + node.appendChild(cdataSection); + } else { + node.setTextContent(value); + } + } + } + } + } + +} + +/** + * ElementMapNodeVisitor + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +class ElementMapVisitor extends DOMHelper.NodeMapVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, + Map result) { + try { + result.put(key, xam.xao.newInstance(ctx, (Element) node)); + } catch (Exception e) { + SofaLogger.error("visitNode error", e); + } + } +} + +/** + * ElementValueMapNodeVisitor + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +class ElementValueMapVisitor extends DOMHelper.NodeMapVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, + Map result) { + String val = node.getTextContent(); + if (xam.trim) { + val = val.trim(); + } + if (xam.valueFactory != null) { + result.put(key, xam.valueFactory.getValue(ctx, val)); + } else { + // TODO: log warning? + result.put(key, val); + } + } +} + +/** + * AttributeValueMapNodeVisitor + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +class AttributeValueMapVisitor extends DOMHelper.NodeMapVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, + Map result) { + String val = node.getNodeValue(); + if (xam.valueFactory != null) { + result.put(key, xam.valueFactory.getValue(ctx, val)); + } else { + // TODO: log warning? + result.put(key, val); + } + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedMember.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedMember.java new file mode 100644 index 000000000..c0163bfc4 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedMember.java @@ -0,0 +1,189 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.common.xmap.annotation.XNode; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.List; +import java.util.Map; + +/** + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedMember { + + public final XMap xmap; + public final XSetter setter; + public final XGetter getter; + public Path path; + public boolean trim; + public boolean cdata; + + // the java type of the described element + public Class type; + // not null if the described object is an xannotated object + public XAnnotatedObject xao; + // the value factory used to transform strings in objects compatible + // with this member type + // In the case of collection types this factory is + // used for collection components + public XValueFactory valueFactory; + + protected XAnnotatedMember(XMap xmap, XSetter setter, XGetter getter) { + this.xmap = xmap; + this.setter = setter; + this.getter = getter; + } + + public XAnnotatedMember(XMap xmap, XSetter setter, XGetter getter, XNode anno) { + this.xmap = xmap; + this.setter = setter; + this.getter = getter; + this.path = new Path(anno.value()); + this.trim = anno.trim(); + this.cdata = anno.cdata(); + this.type = setter.getType(); + this.valueFactory = xmap.getValueFactory(this.type); + this.xao = xmap.register(this.type); + } + + protected void setValue(Object instance, Object value) throws Exception { + setter.setValue(instance, value); + } + + public void process(Context ctx, Element element) throws Exception { + Object value = getValue(ctx, element); + if (value != null) { + setValue(ctx.getObject(), value); + } + } + + public void process(Context ctx, Map map, String keyPrefix) throws Exception { + Object value = getValue(ctx, map, keyPrefix); + if (value != null) { + setValue(ctx.getObject(), value); + } + } + + public void decode(Object instance, Node base, Document document, List filters) + throws Exception { + if (!isFilter(filters)) { + return; + } + + Node node = base; + + int len = path.segments.length; + for (int i = 0; i < len; i++) { + Node n = DOMHelper.getElementNode(node, path.segments[i]); + if (n == null) { + Element element = document.createElement(path.segments[i]); + node = node.appendChild(element); + } else { + node = n; + } + } + + Object object = getter.getValue(instance); + + if (object != null && Element.class.isAssignableFrom(object.getClass())) { + return; + } + + if (xao != null) { + xao.decode(object, node, document, filters); + } else { + String value = object == null ? "" : object.toString(); + + if (path.attribute != null && path.attribute.length() > 0) { + Attr attr = document.createAttribute(path.attribute); + attr.setNodeValue(value); + + ((Element) node).setAttributeNode(attr); + } else { + if (cdata) { + CDATASection cdataSection = document.createCDATASection(value); + node.appendChild(cdataSection); + } else { + node.setTextContent(value); + } + } + } + } + + protected Object getValue(Context ctx, Element base) throws Exception { + if (xao != null) { + Element el = (Element) DOMHelper.getElementNode(base, path); + return el == null ? null : xao.newInstance(ctx, el); + } + // scalar field + if (type == Element.class) { + // allow DOM elements as values + return base; + } + String val = DOMHelper.getNodeValue(base, path); + if (val != null) { + if (trim) { + val = val.trim(); + } + return valueFactory.getValue(ctx, val); + } + return null; + } + + protected Object getValue(Context ctx, Map map, String keyPrefix) + throws Exception { + String key = keyPrefix == null ? path.path : keyPrefix + path.path; + Object val = map.get(key); + Object result = null; + + if (val == null) { + result = null; + } else if (val instanceof String) { + String str = (String) val; + if (str != null) { + if (trim) { + str = str.trim(); + } + result = valueFactory.getValue(ctx, str); + } + } else { + result = val; + } + + return result; + } + + protected boolean isFilter(List filters) { + boolean filter = false; + + if (filters == null || filters.size() == 0) { + filter = true; + } else { + filter = filters.contains(path.path); + } + + return filter; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedObject.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedObject.java new file mode 100644 index 000000000..218c94d07 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedObject.java @@ -0,0 +1,143 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.common.xmap.annotation.XObject; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedObject { + + public final XMap xmap; + public final Class klass; + public final Path path; + + final List members; + + Sorter sorter; + + Sorter deSorter; + + public XAnnotatedObject(XMap xmap, Class klass, XObject xob) { + this.xmap = xmap; + this.klass = klass; + path = new Path(xob.value()); + members = new ArrayList<>(); + String[] order = xob.order(); + if (order.length > 0) { + sorter = new Sorter(order); + } + } + + public void addMember(XAnnotatedMember member) { + members.add(member); + } + + public Path getPath() { + return path; + } + + public Object newInstance(Context ctx, Element element) throws Exception { + Object ob = klass.newInstance(); + ctx.push(ob); + + if (sorter != null) { + Collections.sort(members, sorter); + deSorter = sorter; + sorter = null; // sort only once + } + + // set annotated members + for (XAnnotatedMember member : members) { + member.process(ctx, element); + } + + return ctx.pop(); + } + + public Object newInstance(Context ctx, Map map, String keyPrefix) + throws Exception { + Object ob = klass.newInstance(); + ctx.push(ob); + + // set annotated members + for (XAnnotatedMember member : members) { + member.process(ctx, map, keyPrefix); + } + + return ctx.pop(); + } + + public void decode(Object instance, Node base, Document document, List filters) + throws Exception { + + Node node = base; + String name = path.path; + + if (sorter != null) { + deSorter = sorter; + } + + if (deSorter != null) { + Collections.sort(members, deSorter); + deSorter = null; // sort only once + } + + if (name != null && name.length() > 0) { + Element element = document.createElement(name); + node = node.appendChild(element); + } + + for (XAnnotatedMember annotatedMember : members) { + annotatedMember.decode(instance, node, document, filters); + } + } +} + +class Sorter implements Comparator { + + private final Map order = new HashMap(); + + Sorter(String[] order) { + for (int i = 0; i < order.length; i++) { + this.order.put(order[i], i); + } + } + + public int compare(XAnnotatedMember o1, XAnnotatedMember o2) { + Integer order1 = order.get(o1.path.path); + Integer order2 = order.get(o2.path.path); + int n1 = order1 == null ? Integer.MAX_VALUE : order1; + int n2 = order2 == null ? Integer.MAX_VALUE : order2; + return n1 - n2; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedParent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedParent.java new file mode 100755 index 000000000..a951df185 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XAnnotatedParent.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 com.alipay.sofa.common.xmap; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.List; + +/** + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedParent extends XAnnotatedMember { + + protected XAnnotatedParent(XMap xmap, XSetter setter, XGetter getter) { + super(xmap, setter, getter); + } + + @Override + protected Object getValue(Context ctx, Element base) throws Exception { + return ctx.getParent(); + } + + @Override + public void decode(Object instance, Node base, Document document, List filters) + throws Exception { + // do nothing + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XFieldGetter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XFieldGetter.java new file mode 100755 index 000000000..35dee7267 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XFieldGetter.java @@ -0,0 +1,57 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import java.lang.reflect.Field; + +/** + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @since 2.6.0 + */ + +public class XFieldGetter implements XGetter { + + /** + * field + */ + private Field field; + + public XFieldGetter(Field field) { + this.field = field; + this.field.setAccessible(true); + } + + /** + * @see XGetter#getType() + */ + public Class getType() { + return field.getType(); + } + + /** + * @see XGetter#getValue(Object) + */ + public Object getValue(Object instance) throws Exception { + if (instance == null) { + return null; + } + return field.get(instance); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XFieldSetter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XFieldSetter.java new file mode 100755 index 000000000..04b3ffe95 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XFieldSetter.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 com.alipay.sofa.common.xmap; + +import java.lang.reflect.Field; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XFieldSetter implements XSetter { + + /** + * field + */ + private final Field field; + + public XFieldSetter(Field field) { + this.field = field; + this.field.setAccessible(true); + } + + /** + * @see XSetter#getType() + */ + public Class getType() { + return field.getType(); + } + + /** + * @see XSetter#setValue(Object, Object) + */ + public void setValue(Object instance, Object value) throws IllegalAccessException { + field.set(instance, value); + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XGetter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XGetter.java new file mode 100644 index 000000000..0af5aa17b --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XGetter.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 com.alipay.sofa.common.xmap; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public interface XGetter { + + /** + * Get type + * + * @return Class + */ + Class getType(); + + /** + * Get value + * + * @throws Exception any exception + */ + Object getValue(Object instance) throws Exception; +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMap.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMap.java new file mode 100644 index 000000000..6dadefa78 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMap.java @@ -0,0 +1,624 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.common.xmap.annotation.XContent; +import com.alipay.sofa.common.xmap.annotation.XMemberAnnotation; +import com.alipay.sofa.common.xmap.annotation.XNode; +import com.alipay.sofa.common.xmap.annotation.XNodeList; +import com.alipay.sofa.common.xmap.annotation.XNodeMap; +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.common.xmap.annotation.XParent; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.beans.Introspector; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +/** + * XMap maps an XML file to a java object. + * The mapping is described by annotations on java objects. + * The following annotations are supported: + *
    + *
  • {@link XObject} + * Mark the object as being mappable to an XML node + *
  • {@link XNode} + * Map an XML node to a field of a mappable object + *
  • {@link XNodeList} + * Map an list of XML nodes to a field of a mappable object + *
  • {@link XNodeMap} + * Map an map of XML nodes to a field of a mappable object + *
  • {@link XContent} + * Map an XML node content to a field of a mappable object + *
  • {@link XParent} + * Map a field of the current mappable object to the parent object if any exists + * The parent object is the mappable object containing the current object as a field + *
+ * The mapping is done in 2 steps: + *
    + *
  • The XML file is loaded as a DOM document + *
  • The DOM document is parsed and the nodes mapping is resolved + *
+ * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @author ruoshan + */ + +public class XMap { + + // top level objects + protected final Map roots; + + // the scanned objects + protected final Map objects; + + protected final Map factories; + + private static final String DDD = "http://apache.org/xml/features/disallow-doctype-decl"; + + /** + * Creates a new XMap object. + */ + public XMap() { + objects = new Hashtable<>(); + roots = new Hashtable<>(); + factories = new Hashtable<>(XValueFactory.defaultFactories); + } + + /** + * Gets the value factory used for objects of the given class. + * Value factories are used to decode values from XML strings. + * + * @param type the object type + * @return the value factory if any, null otherwise + */ + public XValueFactory getValueFactory(Class type) { + return factories.get(type); + } + + /** + * Sets a custom value factory for the given class. + * Value factories are used to decode values from XML strings. + * + * @param type the object type + * @param factory the value factory to use for the given type + */ + public void setValueFactory(Class type, XValueFactory factory) { + factories.put(type, factory); + } + + /** + * Gets a list of scanned objects. + * Scanned objects are annotated objects that was registered + * by this XMap instance. + * + * @return list of scanned objects. + */ + public Collection getScannedObjects() { + return objects.values(); + } + + /** + * Gets the root objects. + * Root objects are scanned objects that can be mapped to XML elements + * that are not part from other objects. + * + * @return the root objects + */ + public Collection getRootObjects() { + return roots.values(); + } + + /** + * Registers a mappable object class. + * The class will be scanned for XMap annotations + * and a mapping description is created. + * + * @param klass the object class + * @return the mapping description + */ + @SuppressWarnings("unchecked") + public XAnnotatedObject register(Class klass) { + XAnnotatedObject xao = objects.get(klass); + if (xao == null) { // avoid scanning twice + XObject xob = checkObjectAnnotation(klass, klass.getClassLoader()); + if (xob != null) { + xao = new XAnnotatedObject(this, klass, xob); + objects.put(xao.klass, xao); + scan(xao); + String key = xob.value(); + if (key.length() > 0) { + roots.put(xao.path.path, xao); + } + } + } + return xao; + } + + /** + * Registers a mappable object class. + * The class will be scanned for XMap annotations + * and a mapping description is created. + * + * @param klass the object class + * @return the mapping description + */ + @SuppressWarnings("unchecked") + public XAnnotatedObject register(Class klass, boolean isParent) { + XAnnotatedObject xao = objects.get(klass); + if (xao == null) { // avoid scanning twice + XObject xob = checkObjectAnnotation(klass, klass.getClassLoader()); + if (xob != null) { + xao = new XAnnotatedObject(this, klass, xob); + objects.put(xao.klass, xao); + if (isParent) { + scanParent(xao); + } else { + scan(xao); + } + + String key = xob.value(); + if (key.length() > 0) { + roots.put(xao.path.path, xao); + } + } + } + return xao; + } + + private void scan(XAnnotatedObject xob) { + Field[] fields = xob.klass.getDeclaredFields(); + for (Field field : fields) { + Annotation anno = checkMemberAnnotation(field); + if (anno != null) { + XAnnotatedMember member = createFieldMember(field, anno); + xob.addMember(member); + } + } + + Method[] methods = xob.klass.getDeclaredMethods(); + for (Method method : methods) { + // we accept only methods with one parameter + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { + continue; + } + Annotation anno = checkMemberAnnotation(method); + if (anno != null) { + XAnnotatedMember member = createMethodMember(method, xob.klass, anno); + xob.addMember(member); + } + } + } + + private void scanParent(XAnnotatedObject xob) { + Class scanClass = xob.klass; + + for (; scanClass != Object.class; scanClass = scanClass.getSuperclass()) { + for (Method method : scanClass.getDeclaredMethods()) { + // we accept only methods with one parameter + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { + continue; + } + Annotation anno = checkMemberAnnotation(method); + if (anno != null) { + XAnnotatedMember member = createMethodMember(method, xob.klass, anno); + xob.addMember(member); + } + } + + for (Field field : scanClass.getDeclaredFields()) { + Annotation anno = checkMemberAnnotation(field); + if (anno != null) { + XAnnotatedMember member = createFieldMember(field, anno); + xob.addMember(member); + } + } + } + } + + /** + * Processes the XML file at the given URL using a default context. + * Returns the first registered top level object that is found in the file. + * If not objects are found null is returned. + * + * @param url the XML file url + * @return the first register top level object + * @throws Exception + */ + public Object load(URL url) throws Exception { + return load(new Context(), url.openStream()); + } + + /** + * Processes the XML file at the given URL and using the given contexts. + * Returns the first registered top level object that is found in the file. + * + * @param ctx the context to use + * @param url the XML file url + * @return the first register top level object + * @throws Exception any exception + */ + public Object load(Context ctx, URL url) throws Exception { + return load(ctx, url.openStream()); + } + + /** + * Processes the XML content from the given input stream using a default context. + * Returns the first registered top level object that is found in the file. + * + * @param in the XML input source + * @return the first register top level object + * @throws Exception + */ + public Object load(InputStream in) throws Exception { + return load(new Context(), in); + } + + /** + * Processes the XML content from the given input stream using the given context. + * Return the first registered top level object that is found in the file. + * + * @param ctx the context to use + * @param in the input stream + * @return the first register top level object + * @throws Exception any exception + */ + public Object load(Context ctx, InputStream in) throws Exception { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(DDD, true); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(in); + return load(ctx, document.getDocumentElement()); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Processes the XML file at the given URL using a default context. + * Returns a list with all registered top level objects that are found in the file. + * If not objects are found, an empty list is returned. + * + * @param url the XML file url + * @return a list with all registered top level objects that are found in the file + * @throws Exception + */ + public Object[] loadAll(URL url) throws Exception { + return loadAll(new Context(), url.openStream()); + } + + /** + * Processes the XML file at the given URL using the given context + * Return a list with all registered top level objects that are found in the file. + * If not objects are found an empty list is returned. + * + * @param ctx the context to use + * @param url the XML file url + * @return a list with all registered top level objects that are found in the file + * @throws Exception any exception + */ + public Object[] loadAll(Context ctx, URL url) throws Exception { + return loadAll(ctx, url.openStream()); + } + + /** + * Processes the XML from the given input stream using the given context. + * Returns a list with all registered top level objects that are found in the file. + * If not objects are found, an empty list is returned. + * + * @param in the XML input stream + * @return a list with all registered top level objects that are found in the file + * @throws Exception any exception + */ + public Object[] loadAll(InputStream in) throws Exception { + return loadAll(new Context(), in); + } + + /** + * Processes the XML from the given input stream using the given context. + * Returns a list with all registered top level objects that are found in the file. + * If not objects are found, an empty list is returned. + * + * @param ctx the context to use + * @param in the XML input stream + * @return a list with all registered top level objects that are found in the file + * @throws Exception + */ + public Object[] loadAll(Context ctx, InputStream in) throws Exception { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(DDD, true); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(in); + return loadAll(ctx, document.getDocumentElement()); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Processes the given DOM element and return the first mappable object + * found in the element. + * A default context is used. + * + * @param root the element to process + * @return the first object found in this element or null if none + * @throws Exception + */ + public Object load(Element root) throws Exception { + return load(new Context(), root); + } + + /** + * Processes the given DOM element and return the first mappable object + * found in the element. + * The given context is used. + * + * @param ctx the context to use + * @param root the element to process + * @return the first object found in this element or null if none + * @throws Exception + */ + public Object load(Context ctx, Element root) throws Exception { + // check if the current element is bound to an annotated object + String name = root.getNodeName(); + XAnnotatedObject xob = roots.get(name); + if (xob != null) { + return xob.newInstance(new Context(), root); + } else { + Node p = root.getFirstChild(); + while (p != null) { + if (p.getNodeType() == Node.ELEMENT_NODE) { + // Recurse in the first child Element + return load((Element) p); + } + p = p.getNextSibling(); + } + // We didn't find any Element + return null; + } + } + + @SuppressWarnings("unchecked") + public T load(Map map, String keyPrefix, Class klass) throws Exception { + XAnnotatedObject xob = objects.get(klass); + if (xob == null) { + xob = this.register(klass); + } + + return (T) xob.newInstance(new Context(), map, keyPrefix); + } + + /** + * Processes the given DOM element and return a list with all top-level + * mappable objects found in the element. + * The given context is used. + * + * @param ctx the context to use + * @param root the element to process + * @return the list of all top level objects found + * @throws Exception + */ + public Object[] loadAll(Context ctx, Element root) throws Exception { + List result = new ArrayList(); + loadAll(ctx, root, result); + return result.toArray(); + } + + /** + * Processes the given DOM element and return a list with all top-level + * mappable objects found in the element. + * The default context is used. + * + * @param root the element to process + * @return the list of all top level objects found + * @throws Exception + */ + public Object[] loadAll(Element root) throws Exception { + return loadAll(new Context(), root); + } + + /** + * Same as {@link com.alipay.sofa.common.xmap.XMap#loadAll(Element)} but put collected objects in the + * given collection. + * + * @param root the element to process + * @param result the collection where to collect objects + * @throws Exception + */ + public void loadAll(Element root, Collection result) throws Exception { + loadAll(new Context(), root, result); + } + + public Document asXml(Object contribution, List filters) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature(DDD, true); + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + + if (contribution == null) { + return document; + } + + XAnnotatedObject xao = objects.get(contribution.getClass()); + + if (xao != null) { + xao.decode(contribution, document, document, filters); + } // else TODO + + return document; + } + + public String asXmlString(Object contribution, String encoding, List filters) + throws Exception { + return asXmlString(contribution, encoding, filters, false); + + } + + public String asXmlString(Object contribution, String encoding, List filters, + boolean pretty) throws Exception { + Document doc = asXml(contribution, filters); + + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + DOMSource source = new DOMSource(doc); + if (encoding != null && encoding.length() > 0) { + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + } + if (pretty) { + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + } else { + transformer.setOutputProperty(OutputKeys.INDENT, "no"); + } + + StringWriter sw = new StringWriter(); + + StreamResult result = new StreamResult(sw); + transformer.transform(source, result); + + return sw.toString(); + + } + + /** + * Same as {@link com.alipay.sofa.common.xmap.XMap#loadAll(Context, Element)} but put collected objects in the + * given collection. + * + * @param ctx the context to use + * @param root the element to process + * @param result the collection where to collect objects + * @throws Exception + */ + public void loadAll(Context ctx, Element root, Collection result) throws Exception { + // check if the current element is bound to an annotated object + String name = root.getNodeName(); + XAnnotatedObject xob = roots.get(name); + if (xob != null) { + Object ob = xob.newInstance(ctx, root); + result.add(ob); + } else { + Node p = root.getFirstChild(); + while (p != null) { + if (p.getNodeType() == Node.ELEMENT_NODE) { + loadAll(ctx, (Element) p, result); + } + p = p.getNextSibling(); + } + } + } + + protected static Annotation checkMemberAnnotation(AnnotatedElement ae) { + Annotation[] annos = ae.getAnnotations(); + for (Annotation anno : annos) { + if (anno.annotationType().isAnnotationPresent(XMemberAnnotation.class)) { + return anno; + } + } + return null; + } + + protected static XObject checkObjectAnnotation(AnnotatedElement ae, ClassLoader classLoader) { + return ae.getAnnotation(XObject.class); + } + + private XAnnotatedMember createMember(Annotation annotation, XSetter setter, XGetter getter) { + XAnnotatedMember member = null; + int type = annotation.annotationType().getAnnotation(XMemberAnnotation.class).value(); + if (type == XMemberAnnotation.NODE) { + member = new XAnnotatedMember(this, setter, getter, (XNode) annotation); + } else if (type == XMemberAnnotation.NODE_LIST) { + member = new XAnnotatedList(this, setter, getter, (XNodeList) annotation); + } else if (type == XMemberAnnotation.NODE_MAP) { + member = new XAnnotatedMap(this, setter, getter, (XNodeMap) annotation); + } else if (type == XMemberAnnotation.PARENT) { + member = new XAnnotatedParent(this, setter, getter); + } else if (type == XMemberAnnotation.CONTENT) { + member = new XAnnotatedContent(this, setter, getter, (XContent) annotation); + } + return member; + } + + public final XAnnotatedMember createFieldMember(Field field, Annotation annotation) { + XSetter setter = new XFieldSetter(field); + XGetter getter = new XFieldGetter(field); + return createMember(annotation, setter, getter); + } + + public final XAnnotatedMember createMethodMember(Method method, Class clazz, + Annotation annotation) { + XSetter setter = new XMethodSetter(method); + + String methodName = method.getName(); + String name = Introspector.decapitalize(methodName.substring(3)); + XGetter getter = new XMethodGetter(getGetterMethod(clazz, name), clazz, name); + return createMember(annotation, setter, getter); + } + + public Method getGetterMethod(Class clazz, String name) { + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + String methodName = method.getName(); + if (methodName.matches("^(get|is).*") && method.getParameterTypes().length == 0) { + if (Introspector.decapitalize(methodName.substring(3)).equals(name)) { + return method; + } + } + } + + return null; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMethodGetter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMethodGetter.java new file mode 100755 index 000000000..520cc14d7 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMethodGetter.java @@ -0,0 +1,76 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import java.lang.reflect.Method; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XMethodGetter implements XGetter { + + /** + * method + */ + private Method method; + + /** + * class + */ + private Class clazz; + + /** + * bean name + */ + private String name; + + public XMethodGetter(Method method, Class clazz, String name) { + this.method = method; + this.clazz = clazz; + this.name = name; + } + + /** + * @see XGetter#getType() + */ + public Class getType() { + if (method == null) { + throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + + name); + } + + return method.getReturnType(); + } + + /** + * @see XGetter#getValue(Object) + */ + public Object getValue(Object instance) throws Exception { + if (method == null) { + throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + + name); + } + + if (instance == null) { + return null; + } + return method.invoke(instance, new Object[0]); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMethodSetter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMethodSetter.java new file mode 100755 index 000000000..9f7c4d0ad --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XMethodSetter.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 com.alipay.sofa.common.xmap; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XMethodSetter implements XSetter { + + /** + * method + */ + private final Method method; + + public XMethodSetter(Method method) { + this.method = method; + this.method.setAccessible(true); + } + + /** + * @see XSetter#getType() + */ + public Class getType() { + return method.getParameterTypes()[0]; + } + + /** + * @see XSetter#setValue(Object, Object) + */ + public void setValue(Object instance, Object value) throws IllegalAccessException, + InvocationTargetException { + method.invoke(instance, value); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XSetter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XSetter.java new file mode 100644 index 000000000..ea8a31e91 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XSetter.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 com.alipay.sofa.common.xmap; + +/** + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public interface XSetter { + + /** + * Gets the type of the object to be set by this setter. + * + * @return the setter object type + */ + Class getType(); + + /** + * Sets the value of the underlying member. + * + * @param instance the instance of the object that owns this field + * @param value the value to set + * @throws Exception + */ + void setValue(Object instance, Object value) throws Exception; + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XValueFactory.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XValueFactory.java new file mode 100644 index 000000000..4990428d6 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/XValueFactory.java @@ -0,0 +1,236 @@ +/* + * 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 com.alipay.sofa.common.xmap; + +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import org.w3c.dom.Node; + +import java.io.File; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Hashtable; +import java.util.Map; + +/** + * Value factories are used to decode values from XML strings. + * To register a new factory for a given XMap instance use the method + * {@link com.alipay.sofa.common.xmap.XMap#setValueFactory(Class, com.alipay.sofa.common.xmap.XValueFactory)} + * + * @author Bogdan Stefanescu + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public abstract class XValueFactory { + + static final Map defaultFactories = new Hashtable(); + + public abstract Object getValue(Context ctx, String value); + + public final Object getElementValue(Context ctx, Node element, boolean trim) { + String text = element.getTextContent(); + return getValue(ctx, trim ? text.trim() : text); + } + + public final Object getAttributeValue(Context ctx, Node element, String name) { + Node at = element.getAttributes().getNamedItem(name); + return at != null ? getValue(ctx, at.getNodeValue()) : null; + } + + public static void addFactory(Class klass, XValueFactory factory) { + defaultFactories.put(klass, factory); + } + + public static XValueFactory getFactory(Class type) { + return defaultFactories.get(type); + } + + public static Object getValue(Context ctx, Class klass, String value) { + XValueFactory factory = defaultFactories.get(klass); + if (factory == null) { + return null; + } + return factory.getValue(ctx, value); + } + + public static final XValueFactory STRING = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return value; + } + }; + + public static final XValueFactory INTEGER = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return Integer.valueOf(value); + } + }; + + public static final XValueFactory LONG = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return Long.valueOf(value); + } + }; + + public static final XValueFactory DOUBLE = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return Double.valueOf(value); + } + }; + + public static final XValueFactory FLOAT = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return Float.valueOf(value); + } + }; + + public static final XValueFactory BOOLEAN = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return Boolean.valueOf(value); + } + }; + + public static final XValueFactory DATE = new XValueFactory() { + final DateFormat df = new SimpleDateFormat( + "dd-MM-yyyy"); + + @Override + public Object getValue(Context ctx, String value) { + try { + return df.parse(value); + } catch (Exception e) { + return null; + } + } + }; + + public static final XValueFactory FILE = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + return new File(value); + } + }; + + public static final XValueFactory URL = new XValueFactory() { + @Override + public Object getValue(Context ctx, String value) { + try { + return new java.net.URL(value); + } catch (Exception e) { + return null; + } + } + }; + + public static final XValueFactory CLASS = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + try { + return ctx.loadClass(value); + } catch (Throwable t) { + SofaLogger.error("load class error", t); + return null; + } + } + }; + + public static final XValueFactory RESOURCE = new XValueFactory() { + + @Override + public Object getValue(Context ctx, String value) { + try { + return new Resource( + ctx.getResource(value)); + } catch (Throwable t) { + SofaLogger + .error("new resource error", t); + return null; + } + } + }; + + public static final XValueFactory SHORT = new XValueFactory() { + @Override + public Object getValue(Context ctx, String value) { + return Short.valueOf(value); + } + }; + + public static final XValueFactory CHAR = new XValueFactory() { + @Override + public Object getValue(Context ctx, String value) { + try { + return value.toCharArray()[0]; + } catch (Throwable e) { + return null; + } + } + }; + + public static final XValueFactory BYTE = new XValueFactory() { + @Override + public Object getValue(Context ctx, String value) { + try { + return value.getBytes()[0]; + } catch (Throwable e) { + return null; + } + } + }; + + static { + addFactory(String.class, STRING); + addFactory(Integer.class, INTEGER); + addFactory(Long.class, LONG); + addFactory(Float.class, FLOAT); + addFactory(Double.class, DOUBLE); + addFactory(Date.class, DATE); + addFactory(Boolean.class, BOOLEAN); + addFactory(File.class, FILE); + addFactory(java.net.URL.class, URL); + addFactory(Short.class, SHORT); + addFactory(Character.class, CHAR); + addFactory(Byte.class, BYTE); + + addFactory(int.class, INTEGER); + addFactory(long.class, LONG); + addFactory(double.class, DOUBLE); + addFactory(float.class, FLOAT); + addFactory(boolean.class, BOOLEAN); + addFactory(short.class, SHORT); + addFactory(char.class, CHAR); + addFactory(byte.class, BYTE); + + addFactory(Class.class, CLASS); + addFactory(Resource.class, RESOURCE); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XContent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XContent.java new file mode 100755 index 000000000..6460fd3d4 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XContent.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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that injects the content of the current node as an XML string or + * DocumentFragment depending on the field type. + * + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.CONTENT) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XContent { + + /** + * An xpathy expression specifying the XML node to bind to. + * + * @return the node xpath + */ + String value() default ""; + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XMemberAnnotation.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XMemberAnnotation.java new file mode 100644 index 000000000..83dd7f1b8 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XMemberAnnotation.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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to identify XMap annotations. + *

+ * This annotation has a single parameter "value" of type int that specifies the type of the annotation. + * + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface XMemberAnnotation { + + int NODE = 1; + int NODE_LIST = 2; + int NODE_MAP = 3; + int PARENT = 4; + int CONTENT = 5; + int NODE_SPRING = 6; + int NODE_LIST_SPRING = 7; + int NODE_MAP_SPRING = 8; + + /** + * The type of the annotation. + * + * @return the type of the annotation. + */ + int value(); + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNode.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNode.java new file mode 100644 index 000000000..6b3f5216a --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNode.java @@ -0,0 +1,50 @@ +/* + * 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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.NODE) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XNode { + + /** + * An xpathy expression specifying the XML node to bind to. + */ + String value() default ""; + + /** + * Whether to trim text content for element nodes. + *

+ * Ignored for attribute nodes. + *

+ */ + boolean trim() default true; + + /** + * is or not cdata + */ + boolean cdata() default false; +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNodeList.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNodeList.java new file mode 100644 index 000000000..889728137 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNodeList.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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.NODE_LIST) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XNodeList { + + /** + * A path expression specifying the XML node to bind to. + * + * @return the node xpath + */ + String value(); + + /** + * Whether to trim text content for element nodes. + * + * @return + */ + boolean trim() default true; + + /** + * The type of a collection object. + * + * @return the type of items + */ + Class type(); + + /** + * The type of the objects in this collection. + * + * @return the type of items + */ + Class componentType(); + + /** + * is or not cdata + */ + boolean cdata() default false; + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNodeMap.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNodeMap.java new file mode 100644 index 000000000..cc5270a31 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XNodeMap.java @@ -0,0 +1,75 @@ +/* + * 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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.NODE_MAP) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XNodeMap { + + /** + * A path expression specifying the XML node to bind to. + * + * @return the node xpath + */ + String value(); + + /** + * Whether to trim text content for element nodes. + * + * @return + */ + boolean trim() default true; + + /** + * The path relative to the current node + * (which is located by {@link com.alipay.sofa.common.xmap.annotation.XNodeMap#value()}) which contain + * the map key to be used. + * + * @return + */ + String key(); + + /** + * The type of collection object. + * + * @return the type of items + */ + Class type(); + + /** + * The type of the objects in this collection. + * + * @return the type of items + */ + Class componentType(); + + /** + * is or not cdata + */ + boolean cdata() default false; + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XObject.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XObject.java new file mode 100644 index 000000000..731c0cacf --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XObject.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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface XObject { + + /** + * An xpath expression specifying the XML node to bind to. + * + * @return the node xpath + */ + String value() default ""; + + /** + * + * @return + */ + String[] order() default {}; + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XParent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XParent.java new file mode 100755 index 000000000..e4ce29c00 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/XParent.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 com.alipay.sofa.common.xmap.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Bogdan Stefanescu + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.PARENT) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XParent { + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedListSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedListSpring.java new file mode 100755 index 000000000..9b5fc1d4d --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedListSpring.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 com.alipay.sofa.common.xmap.annotation.spring; + +import com.alipay.sofa.common.xmap.Context; +import com.alipay.sofa.common.xmap.DOMHelper; +import com.alipay.sofa.common.xmap.Path; +import com.alipay.sofa.common.xmap.XAnnotatedList; +import com.alipay.sofa.common.xmap.XAnnotatedMember; +import com.alipay.sofa.common.xmap.XGetter; +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.common.xmap.XSetter; +import com.alipay.sofa.common.xmap.spring.XNodeListSpring; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public class XAnnotatedListSpring extends XAnnotatedList { + + /** + * dom visitor + */ + protected static final ElementValueVisitor elementVisitor = new ElementValueVisitor(); + protected static final AttributeValueVisitor attributeVisitor = new AttributeValueVisitor(); + + private XAnnotatedSpringObject xaso; + + protected XAnnotatedListSpring(XMap xmap, XSetter setter, XGetter getter) { + super(xmap, setter, getter); + } + + public XAnnotatedListSpring(XMap xmap, XSetter setter, XGetter getter, XNodeListSpring anno, + XAnnotatedSpringObject xaso) { + super(xmap, setter, getter); + path = new Path(anno.value()); + trim = anno.trim(); + type = anno.type(); + componentType = anno.componentType(); + this.xaso = xaso; + } + + @SuppressWarnings("unchecked") + @Override + protected Object getValue(Context ctx, Element base) throws Exception { + ArrayList values = new ArrayList(); + if (path.attribute != null) { + // attribute list + DOMHelper.visitNodes(ctx, this, base, path, attributeVisitor, values); + } else { + // element list + DOMHelper.visitNodes(ctx, this, base, path, elementVisitor, values); + } + + if (type != ArrayList.class) { + if (type.isArray() && !componentType.isPrimitive()) { + values.toArray((Object[]) Array.newInstance(componentType, values.size())); + } else { + Collection col = (Collection) type.newInstance(); + col.addAll(values); + return col; + } + } + return values; + } + + public void setXaso(XAnnotatedSpringObject xaso) { + this.xaso = xaso; + } + + public XAnnotatedSpringObject getXaso() { + return xaso; + } +} + +class ElementValueVisitor extends DOMHelper.NodeVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection result) { + String val = node.getTextContent(); + if (val != null && val.length() > 0) { + if (xam.trim) + val = val.trim(); + Object object = XMapSpringUtil.getSpringObject( + ((XAnnotatedListSpring) xam).componentType, val, ((XAnnotatedListSpring) xam) + .getXaso().getApplicationContext()); + if (object != null) + result.add(object); + } + } +} + +class AttributeValueVisitor extends DOMHelper.NodeVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, Collection result) { + String val = node.getNodeValue(); + if (val != null && val.length() > 0) { + if (xam.trim) + val = val.trim(); + Object object = XMapSpringUtil.getSpringObject( + ((XAnnotatedListSpring) xam).componentType, val, ((XAnnotatedListSpring) xam) + .getXaso().getApplicationContext()); + if (object != null) + result.add(object); + } + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedMapSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedMapSpring.java new file mode 100755 index 000000000..93b2245db --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedMapSpring.java @@ -0,0 +1,118 @@ +/* + * 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 com.alipay.sofa.common.xmap.annotation.spring; + +import com.alipay.sofa.common.xmap.Context; +import com.alipay.sofa.common.xmap.DOMHelper; +import com.alipay.sofa.common.xmap.Path; +import com.alipay.sofa.common.xmap.XAnnotatedMap; +import com.alipay.sofa.common.xmap.XAnnotatedMember; +import com.alipay.sofa.common.xmap.XGetter; +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.common.xmap.XSetter; +import com.alipay.sofa.common.xmap.spring.XNodeMapSpring; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.Map; + +/** + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public class XAnnotatedMapSpring extends XAnnotatedMap { + + /** + * dom visitor + */ + protected static final ElementValueMapVisitor elementVisitor = new ElementValueMapVisitor(); + protected static final AttributeValueMapVisitor attributeVisitor = new AttributeValueMapVisitor(); + + private XAnnotatedSpringObject xaso; + + public XAnnotatedMapSpring(XMap xmap, XSetter setter, XGetter getter, XNodeMapSpring anno, + XAnnotatedSpringObject xaso) { + super(xmap, setter, getter, null); + this.setXaso(xaso); + path = new Path(anno.value()); + trim = anno.trim(); + key = new Path(anno.key()); + type = anno.type(); + componentType = anno.componentType(); + } + + @SuppressWarnings("unchecked") + @Override + protected Object getValue(Context ctx, Element base) throws IllegalAccessException, + InstantiationException { + Map values = (Map) type.newInstance(); + if (path.attribute != null) { + // attribute list + DOMHelper.visitMapNodes(ctx, this, base, path, attributeVisitor, values); + } else { + // element list + DOMHelper.visitMapNodes(ctx, this, base, path, elementVisitor, values); + } + return values; + } + + public void setXaso(XAnnotatedSpringObject xaso) { + this.xaso = xaso; + } + + public XAnnotatedSpringObject getXaso() { + return xaso; + } +} + +class ElementValueMapVisitor extends DOMHelper.NodeMapVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, + Map result) { + String val = node.getTextContent(); + if (val != null && val.length() > 0) { + if (xam.trim) + val = val.trim(); + Object object = XMapSpringUtil.getSpringObject( + ((XAnnotatedMapSpring) xam).componentType, val, ((XAnnotatedMapSpring) xam) + .getXaso().getApplicationContext()); + if (object != null) + result.put(key, object); + } + } +} + +class AttributeValueMapVisitor extends DOMHelper.NodeMapVisitor { + + @Override + public void visitNode(Context ctx, XAnnotatedMember xam, Node node, String key, + Map result) { + String val = node.getNodeValue(); + if (val != null && val.length() > 0) { + if (xam.trim) + val = val.trim(); + Object object = XMapSpringUtil.getSpringObject( + ((XAnnotatedMapSpring) xam).componentType, val, ((XAnnotatedMapSpring) xam) + .getXaso().getApplicationContext()); + if (object != null) + result.put(key, object); + } + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedSpring.java new file mode 100755 index 000000000..19df1f034 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedSpring.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 com.alipay.sofa.common.xmap.annotation.spring; + +import com.alipay.sofa.common.xmap.Context; +import com.alipay.sofa.common.xmap.Path; +import com.alipay.sofa.common.xmap.XAnnotatedMember; +import com.alipay.sofa.common.xmap.XGetter; +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.common.xmap.XSetter; +import com.alipay.sofa.common.xmap.spring.XNodeSpring; +import org.w3c.dom.Element; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XAnnotatedSpring extends XAnnotatedMember { + + public XAnnotatedSpringObject xaso; + + protected XAnnotatedSpring(XMap xmap, XSetter setter, XGetter getter) { + super(xmap, setter, getter); + } + + public XAnnotatedSpring(XMap xmap, XSetter setter, XGetter getter, XNodeSpring anno, + XAnnotatedSpringObject xaso) { + super(xmap, setter, getter); + path = new Path(anno.value()); + trim = anno.trim(); + type = setter.getType(); + this.xaso = xaso; + } + + @SuppressWarnings("unchecked") + @Override + protected Object getValue(Context ctx, Element base) throws Exception { + // scalar field + if (type == Element.class) { + // allow DOM elements as values + return base; + } + return XMapSpringUtil.getSpringOjbect(this, xaso.getApplicationContext(), base); + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedSpringObject.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedSpringObject.java new file mode 100755 index 000000000..b260162f7 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XAnnotatedSpringObject.java @@ -0,0 +1,46 @@ +/* + * 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 com.alipay.sofa.common.xmap.annotation.spring; + +import com.alipay.sofa.common.xmap.XAnnotatedObject; +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.common.xmap.annotation.XObject; +import org.springframework.context.ApplicationContext; + +/** + * @author xi.hux@alipay.com + * @since 2.6.0 + * */ +public class XAnnotatedSpringObject extends XAnnotatedObject { + + private ApplicationContext applicationContext; + + public XAnnotatedSpringObject(XMap xmap, Class klass, XObject xob, + ApplicationContext applicationContext) { + super(xmap, klass, xob); + this.applicationContext = applicationContext; + } + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XMapSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XMapSpring.java new file mode 100644 index 000000000..4ddfeaf2c --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XMapSpring.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 com.alipay.sofa.common.xmap.annotation.spring; + +import com.alipay.sofa.common.xmap.XAnnotatedMember; +import com.alipay.sofa.common.xmap.XAnnotatedObject; +import com.alipay.sofa.common.xmap.XFieldGetter; +import com.alipay.sofa.common.xmap.XFieldSetter; +import com.alipay.sofa.common.xmap.XGetter; +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.common.xmap.XMethodGetter; +import com.alipay.sofa.common.xmap.XMethodSetter; +import com.alipay.sofa.common.xmap.XSetter; +import com.alipay.sofa.common.xmap.annotation.XMemberAnnotation; +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.common.xmap.spring.XNodeListSpring; +import com.alipay.sofa.common.xmap.spring.XNodeMapSpring; +import com.alipay.sofa.common.xmap.spring.XNodeSpring; +import org.springframework.context.ApplicationContext; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Integrate XMap with spring + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XMapSpring extends XMap { + + @SuppressWarnings("unchecked") + public XAnnotatedObject register(Class klass, ApplicationContext applicationContext) { + XAnnotatedObject xao = objects.get(klass); + if (xao == null) { // avoid scanning twice + XObject xob = checkObjectAnnotation(klass, klass.getClassLoader()); + if (xob != null) { + xao = new XAnnotatedSpringObject(this, klass, xob, applicationContext); + objects.put(xao.klass, xao); + scan(xao); + String key = xob.value(); + if (key.length() > 0) { + roots.put(xao.path.path, xao); + } + } + } + return xao; + } + + /** + * Scan Field + * + * @param xob XAnnotated Object + */ + private void scan(XAnnotatedObject xob) { + Field[] fields = xob.klass.getDeclaredFields(); + for (Field field : fields) { + Annotation anno = checkMemberAnnotation(field); + if (anno != null) { + XAnnotatedMember member = createFieldMember(field, anno); + if (member == null) { + member = createExtendFieldMember(field, anno, xob); + } + xob.addMember(member); + } + } + + Method[] methods = xob.klass.getDeclaredMethods(); + for (Method method : methods) { + // we accept only methods with one parameter + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) { + continue; + } + Annotation anno = checkMemberAnnotation(method); + if (anno != null) { + XAnnotatedMember member = createMethodMember(method, xob.klass, anno); + if (member == null) { + member = createExtendMethodMember(method, anno, xob); + } + xob.addMember(member); + } + } + } + + private XAnnotatedMember createExtendFieldMember(Field field, Annotation annotation, + XAnnotatedObject xob) { + XSetter setter = new XFieldSetter(field); + XGetter getter = new XFieldGetter(field); + return createExtendMember(annotation, setter, getter, xob); + } + + public final XAnnotatedMember createExtendMethodMember(Method method, Annotation annotation, + XAnnotatedObject xob) { + XSetter setter = new XMethodSetter(method); + XGetter getter = new XMethodGetter(null, null, null); + return createExtendMember(annotation, setter, getter, xob); + } + + private XAnnotatedMember createExtendMember(Annotation annotation, XSetter setter, + XGetter getter, XAnnotatedObject xob) { + XAnnotatedMember member = null; + int type = annotation.annotationType().getAnnotation(XMemberAnnotation.class).value(); + if (type == XMemberAnnotation.NODE_SPRING) { + member = new XAnnotatedSpring(this, setter, getter, (XNodeSpring) annotation, + (XAnnotatedSpringObject) xob); + } else if (type == XMemberAnnotation.NODE_LIST_SPRING) { + member = new XAnnotatedListSpring(this, setter, getter, (XNodeListSpring) annotation, + (XAnnotatedSpringObject) xob); + } else if (type == XMemberAnnotation.NODE_MAP_SPRING) { + member = new XAnnotatedMapSpring(this, setter, getter, (XNodeMapSpring) annotation, + (XAnnotatedSpringObject) xob); + } + return member; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XMapSpringUtil.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XMapSpringUtil.java new file mode 100755 index 000000000..17be7388a --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/annotation/spring/XMapSpringUtil.java @@ -0,0 +1,67 @@ +/* + * 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 com.alipay.sofa.common.xmap.annotation.spring; + +import com.alipay.sofa.common.xmap.DOMHelper; +import com.alipay.sofa.common.xmap.XAnnotatedMember; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.w3c.dom.Element; + +/** + * XMapSpring util + * @author xi.hux@alipay.com + * @since 2.6.0 + * + */ +public class XMapSpringUtil { + + /** + * Get spring object + * @param type type + * @param beanName bean name + * @param applicationContext application context + * @return spring object + */ + public static Object getSpringObject(Class type, String beanName, + ApplicationContext applicationContext) { + if (type == Resource.class) { + return applicationContext.getResource(beanName); + } else { + return applicationContext.getBean(beanName, type); + } + } + + /** + * Get spring object + * @param xam XAnnotated member + * @param applicationContext application context + * @param base element base + * @return + */ + public static Object getSpringOjbect(XAnnotatedMember xam, + ApplicationContext applicationContext, Element base) { + String val = DOMHelper.getNodeValue(base, xam.path); + if (val != null && val.length() > 0) { + if (xam.trim) { + val = val.trim(); + } + return getSpringObject(xam.type, val, applicationContext); + } + return null; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeListSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeListSpring.java new file mode 100644 index 000000000..871170b55 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeListSpring.java @@ -0,0 +1,62 @@ +/* + * 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 com.alipay.sofa.common.xmap.spring; + +import com.alipay.sofa.common.xmap.annotation.XMemberAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.NODE_LIST_SPRING) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XNodeListSpring { + + /** + * Get the node xpath + * + * @return the node xpath + */ + String value(); + + /** + * Get whether need to trim or not + * + * @return trim or not + */ + boolean trim() default true; + + /** + * Get the type of items + * + * @return the type of items + */ + Class type(); + + /** + * Get the type of items + * + * @return the type of items + */ + Class componentType(); +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeMapSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeMapSpring.java new file mode 100755 index 000000000..ab60e9694 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeMapSpring.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 com.alipay.sofa.common.xmap.spring; + +import com.alipay.sofa.common.xmap.annotation.XMemberAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.NODE_MAP_SPRING) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XNodeMapSpring { + + /** + * Get the node xpath + * + * @return the node xpath + */ + String value(); + + /** + * Whether need to trim or not + * + * @return trim or not + */ + boolean trim() default true; + + /** + * Get the key path + * + * @return key path + */ + String key(); + + /** + * Get the type of items + * + * @return the type of items + */ + Class type(); + + /** + * Get the type of items + * + * @return the type of items + */ + Class componentType(); +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeSpring.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeSpring.java new file mode 100755 index 000000000..dec644585 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/common/xmap/spring/XNodeSpring.java @@ -0,0 +1,56 @@ +/* + * 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 com.alipay.sofa.common.xmap.spring; + +import com.alipay.sofa.common.xmap.annotation.XMemberAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +@XMemberAnnotation(XMemberAnnotation.NODE_SPRING) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface XNodeSpring { + + /** + * Get the node xpath + * + * @return the node xpath + */ + String value(); + + /** + * Whether need to trim or not + * + * @return trim or not + */ + boolean trim() default true; + + /** + * Get the type of items + * + * @return the type of items + */ + Class type() default Object.class; + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaParameter.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaParameter.java new file mode 100644 index 000000000..ddc88c17f --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaParameter.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 com.alipay.sofa.runtime.api.annotation; + +/** + * Configure parameters by annotation + * + * @author ScienJus + */ +public @interface SofaParameter { + + /** + * Parameter key + * @return + */ + String key(); + + /** + * Parameter value + * @return + */ + String value(); +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaReferenceBinding.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaReferenceBinding.java index 635cf151b..3de1eda97 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaReferenceBinding.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaReferenceBinding.java @@ -101,13 +101,30 @@ String registry() default ""; /** - * dalay init connection + * delay init connection * * @return */ boolean lazy() default false; + /** + * specify serialize type + * + * @return + */ String serializeType() default ""; + /** + * specify load balance type + * + * @return + */ String loadBalancer() default ""; + + /** + * parameters of consumer + * + * @return parameters of consumer + */ + SofaParameter[] parameters() default {}; } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaServiceBinding.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaServiceBinding.java index 1b23e52dd..80c8efbdf 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaServiceBinding.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/annotation/SofaServiceBinding.java @@ -56,7 +56,7 @@ String[] filters() default {}; /** - * custorm thread pool for current service + * custom thread pool for current service * * @return custom thread pool */ @@ -76,5 +76,17 @@ */ int timeout() default 3000; + /** + * specify serialize type + * + * @return + */ String serializeType() default ""; + + /** + * parameters of service + * + * @return parameters of service + */ + SofaParameter[] parameters() default {}; } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/aware/ExtensionClientAware.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/aware/ExtensionClientAware.java new file mode 100644 index 000000000..b33f0ee04 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/aware/ExtensionClientAware.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 com.alipay.sofa.runtime.api.aware; + +import com.alipay.sofa.runtime.api.client.ExtensionClient; + +/** + * Interface used to implemented by a Spring bean who want to get an instance of {@link ExtensionClient}. Sample usage: + * + *
+ *
+ * public class ExtensionClientBean implements ExtensionClientAware {
+ *
+ *     private ExtensionClient extensionClient;
+ *
+ *     @Override
+ *     public void setExtensionClient(ExtensionClient extensionClient) {
+ *         this.clientFactory = extensionClient;
+ *     }
+ *
+ *     public ExtensionClient getClientFactory() {
+ *         return extensionClient;
+ *     }
+ * }
+ *
+ * 
+ * + * @author ruoshan + * @since 2.6.0 + */ +public interface ExtensionClientAware { + + /** + * Set the instance of {@link ExtensionClient} to the Spring bean that implement this interface. + * + * @param extensionClient ExtensionClient The instance of {@link ExtensionClient} + */ + void setExtensionClient(ExtensionClient extensionClient); +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/ExtensionClient.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/ExtensionClient.java new file mode 100644 index 000000000..27d31775d --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/ExtensionClient.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 com.alipay.sofa.runtime.api.client; + +import com.alipay.sofa.runtime.api.client.param.ExtensionParam; +import com.alipay.sofa.runtime.api.client.param.ExtensionPointParam; + +/** + * Programming API for Extension/Extension-Point + * + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public interface ExtensionClient { + + /** + * publish extension + * + * @param extensionParam extension param + */ + void publishExtension(ExtensionParam extensionParam); + + /** + * publish extension-point + * + * @param extensionPointParam extension point param + */ + void publishExtensionPoint(ExtensionPointParam extensionPointParam); +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/param/ExtensionParam.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/param/ExtensionParam.java new file mode 100644 index 000000000..5c4117407 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/param/ExtensionParam.java @@ -0,0 +1,89 @@ +/* + * 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 com.alipay.sofa.runtime.api.client.param; + +import org.w3c.dom.Element; + +/** + * Extension param + * + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionParam { + // target extension point name + private String targetName; + // target bean name of extension + private String targetInstanceName; + // XML Contribution element + private Element element; + + /** + * Get the target extension point name + * + * @return The target extension point name + */ + public String getTargetName() { + return targetName; + } + + /** + * Set the target bean name of extension + * + * @param targetName The target bean name of extension + */ + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + /** + * Get the XML Contribution element + * + * @return The XML Contribution element + */ + public Element getElement() { + return element; + } + + /** + * Set the XML Contribution element + * + * @param element The XML Contribution element + */ + public void setElement(Element element) { + this.element = element; + } + + /** + * Get the target bean name of extension + * + * @return The target target bean name of extension + */ + public String getTargetInstanceName() { + return targetInstanceName; + } + + /** + * Set the target bean name of extension + * + * @param targetInstanceName The the target bean name of extension + */ + public void setTargetInstanceName(String targetInstanceName) { + this.targetInstanceName = targetInstanceName; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/param/ExtensionPointParam.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/param/ExtensionPointParam.java new file mode 100644 index 000000000..86d23d60d --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/client/param/ExtensionPointParam.java @@ -0,0 +1,108 @@ +/* + * 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 com.alipay.sofa.runtime.api.client.param; + +/** + * Extension-Point Param + * + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionPointParam { + // name of the extension point + private String name; + // target bean name of the extension point + private String targetName; + // target bean of the extension point + private Object target; + // contribution description class + private Class contributionClass; + + /** + * Get the name of the extension point + * + * @return The name of the extension point + */ + public String getName() { + return name; + } + + /** + * Set the name of the extension point + * + * @param name The name of the extension point + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get target bean name of the extension point + * + * @return The name of the extension point + */ + public String getTargetName() { + return targetName; + } + + /** + * Set the target bean name of the extension point + * + * @param targetName The target bean name of the extension point + */ + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + /** + * Get target bean of the extension point + * + * @return The bean of the extension point + */ + public Object getTarget() { + return target; + } + + /** + * Set the target bean of the extension point + * + * @param target The target bean of the extension point + */ + public void setTarget(Object target) { + this.target = target; + } + + /** + * Get contribution description class of the extension point + * + * @return The contribution description class of the extension point + */ + public Class getContributionClass() { + return contributionClass; + } + + /** + * Set contribution description class of the extension point + * + * @param contributionClass The contribution description class of the extension point + */ + public void setContributionClass(Class contributionClass) { + this.contributionClass = contributionClass; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/component/ComponentLifeCycle.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/component/ComponentLifeCycle.java new file mode 100644 index 000000000..9d0d30705 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/api/component/ComponentLifeCycle.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 com.alipay.sofa.runtime.api.component; + +import com.alipay.sofa.runtime.api.ServiceRuntimeException; + +/** + * Interface used to implemented by components who wants to do something when certain lifecycle event of the component. + * + * @author khotyn + * @since 2.6.0 + */ +public interface ComponentLifeCycle { + /** + * Component lifecycle event occurred when component is activated. + * + * @throws ServiceRuntimeException + */ + void activate() throws ServiceRuntimeException; + + /** + * Component lifecycle event occurred when component is deactivated. + * + * @throws ServiceRuntimeException + */ + void deactivate() throws ServiceRuntimeException; +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/component/impl/ComponentManagerImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/component/impl/ComponentManagerImpl.java index c6463b31c..e616f7bc2 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/component/impl/ComponentManagerImpl.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/component/impl/ComponentManagerImpl.java @@ -189,6 +189,25 @@ public Collection getComponentInfosByType(ComponentType type) { return componentInfos; } + @Override + public void resolvePendingResolveComponent(ComponentName componentName) { + ComponentInfo componentInfo = registry.get(componentName); + + if (componentInfo.isResolved()) { + return; + } + + if (componentInfo.resolve()) { + typeRegistry(componentInfo); + try { + componentInfo.activate(); + } catch (Throwable e) { + componentInfo.exception(new Exception(e)); + SofaLogger.error(e, "Failed to create the component " + componentInfo.getName()); + } + } + } + private void typeRegistry(ComponentInfo componentInfo) { ComponentName name = componentInfo.getName(); if (name != null) { diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/client/ExtensionClientImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/client/ExtensionClientImpl.java new file mode 100644 index 000000000..fe4dc94b3 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/client/ExtensionClientImpl.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 com.alipay.sofa.runtime.ext.client; + +import com.alipay.sofa.runtime.api.client.ExtensionClient; +import com.alipay.sofa.runtime.api.client.param.ExtensionParam; +import com.alipay.sofa.runtime.api.client.param.ExtensionPointParam; +import com.alipay.sofa.runtime.ext.component.ExtensionComponent; +import com.alipay.sofa.runtime.ext.component.ExtensionImpl; +import com.alipay.sofa.runtime.ext.component.ExtensionPointComponent; +import com.alipay.sofa.runtime.ext.component.ExtensionPointImpl; +import com.alipay.sofa.runtime.spi.component.ComponentInfo; +import com.alipay.sofa.runtime.spi.component.DefaultImplementation; +import com.alipay.sofa.runtime.spi.component.Implementation; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import com.alipay.sofa.runtime.spi.util.ComponentNameFactory; +import com.alipay.sofa.service.api.component.ExtensionPoint; +import org.springframework.util.Assert; + +/** + * Programming API Implement for Extension/Extension-Point + * + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionClientImpl implements ExtensionClient { + + private SofaRuntimeContext sofaRuntimeContext; + + public ExtensionClientImpl(SofaRuntimeContext sofaRuntimeContext) { + this.sofaRuntimeContext = sofaRuntimeContext; + } + + @Override + public void publishExtension(ExtensionParam extensionParam) { + Assert.notNull(extensionParam, "extensionParam can not be null."); + Assert.notNull(extensionParam.getElement(), + "Extension contribution element can not be null."); + Assert.notNull(extensionParam.getTargetInstanceName(), + "Extension target instance name can not be null."); + Assert.notNull(extensionParam.getTargetName(), "Extension target name can not be null."); + + ExtensionImpl extension = new ExtensionImpl(null, extensionParam.getTargetName(), + extensionParam.getElement(), sofaRuntimeContext.getAppClassLoader()); + extension.setTargetComponentName(ComponentNameFactory.createComponentName( + ExtensionPointComponent.EXTENSION_POINT_COMPONENT_TYPE, + extensionParam.getTargetInstanceName() + ExtensionComponent.LINK_SYMBOL + + extensionParam.getTargetName())); + ComponentInfo extensionComponent = new ExtensionComponent(extension, sofaRuntimeContext); + sofaRuntimeContext.getComponentManager().register(extensionComponent); + } + + @Override + public void publishExtensionPoint(ExtensionPointParam extensionPointParam) { + Assert.notNull(extensionPointParam, "extensionPointParam can not be null."); + Assert.notNull(extensionPointParam.getName(), "Extension point name can not be null."); + Assert.notNull(extensionPointParam.getContributionClass(), + "Extension point contribution can not be null."); + Assert.notNull(extensionPointParam.getTarget(), "Extension point target can not be null."); + + ExtensionPoint extensionPoint = new ExtensionPointImpl(extensionPointParam.getName(), + extensionPointParam.getContributionClass()); + Implementation implementation = new DefaultImplementation( + extensionPointParam.getTargetName()); + implementation.setTarget(extensionPointParam.getTarget()); + ComponentInfo extensionPointComponent = new ExtensionPointComponent(extensionPoint, + sofaRuntimeContext, implementation); + sofaRuntimeContext.getComponentManager().register(extensionPointComponent); + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionComponent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionComponent.java new file mode 100644 index 000000000..f5573fd72 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionComponent.java @@ -0,0 +1,147 @@ +/* + * 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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.runtime.api.ServiceRuntimeException; +import com.alipay.sofa.runtime.api.component.ComponentName; +import com.alipay.sofa.runtime.api.component.Property; +import com.alipay.sofa.runtime.model.ComponentStatus; +import com.alipay.sofa.runtime.model.ComponentType; +import com.alipay.sofa.runtime.spi.component.AbstractComponent; +import com.alipay.sofa.runtime.spi.component.ComponentInfo; +import com.alipay.sofa.runtime.spi.component.ComponentManager; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import com.alipay.sofa.runtime.spi.health.HealthResult; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import com.alipay.sofa.runtime.spi.util.ComponentNameFactory; +import com.alipay.sofa.service.api.component.Extensible; +import com.alipay.sofa.service.api.component.Extension; +import com.alipay.sofa.service.api.component.ExtensionPoint; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * SOFA Extension Component + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionComponent extends AbstractComponent { + public static final String LINK_SYMBOL = "$"; + public static final ComponentType EXTENSION_COMPONENT_TYPE = new ComponentType("extension"); + + private Extension extension; + + public ExtensionComponent(Extension extension, SofaRuntimeContext sofaRuntimeContext) { + this.extension = extension; + this.sofaRuntimeContext = sofaRuntimeContext; + this.componentName = ComponentNameFactory.createComponentName( + EXTENSION_COMPONENT_TYPE, + extension.getTargetComponentName().getName() + LINK_SYMBOL + + ObjectUtils.getIdentityHexString(extension)); + } + + @Override + public ComponentType getType() { + return EXTENSION_COMPONENT_TYPE; + } + + @Override + public Map getProperties() { + return null; + } + + @Override + public boolean resolve() { + if (componentStatus != ComponentStatus.REGISTERED) { + return false; + } + + ComponentManager componentManager = sofaRuntimeContext.getComponentManager(); + ComponentName extensionPointComponentName = extension.getTargetComponentName(); + + ComponentInfo extensionPointComponentInfo = componentManager + .getComponentInfo(extensionPointComponentName); + + if (extensionPointComponentInfo != null && extensionPointComponentInfo.isActivated()) { + componentStatus = ComponentStatus.RESOLVED; + return true; + } + + return false; + } + + @Override + public void activate() throws ServiceRuntimeException { + if (componentStatus != ComponentStatus.RESOLVED) { + return; + } + + ComponentManager componentManager = sofaRuntimeContext.getComponentManager(); + ComponentName extensionPointComponentName = extension.getTargetComponentName(); + ComponentInfo extensionPointComponentInfo = componentManager + .getComponentInfo(extensionPointComponentName); + + if (extensionPointComponentInfo == null || !extensionPointComponentInfo.isActivated()) { + return; + } + + loadContributions( + ((ExtensionPointComponent) extensionPointComponentInfo).getExtensionPoint(), extension); + + Object target = extensionPointComponentInfo.getImplementation().getTarget(); + if (target instanceof Extensible) { + try { + ((Extensible) target).registerExtension(extension); + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } else { + Method method = ReflectionUtils.findMethod(target.getClass(), "registerExtension", + Extension.class); + ReflectionUtils.invokeMethod(method, target, extension); + } + + componentStatus = ComponentStatus.ACTIVATED; + } + + @Override + public HealthResult isHealthy() { + HealthResult healthResult = new HealthResult(componentName.getRawName()); + healthResult.setHealthy(true); + return healthResult; + } + + public Extension getExtension() { + return extension; + } + + private void loadContributions(ExtensionPoint extensionPoint, Extension extension) { + if (extensionPoint != null && extensionPoint.hasContribution()) { + try { + Object[] contribs = ((ExtensionPointInternal) extensionPoint) + .loadContributions((ExtensionInternal) extension); + ((ExtensionInternal) extension).setContributions(contribs); + } catch (Exception e) { + SofaLogger.error(e, "Failed to create contribution objects"); + } + } + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionImpl.java new file mode 100644 index 000000000..a0a0d20d7 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionImpl.java @@ -0,0 +1,117 @@ +/* + * 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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.runtime.api.component.ComponentName; +import org.w3c.dom.Element; + +import java.io.Serializable; + +/** + * Extension Implement + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ + +public class ExtensionImpl implements ExtensionInternal, Serializable { + + private static final long serialVersionUID = 14648778982899384L; + protected ComponentName name; + protected ComponentName target; + protected String extensionPoint; + protected String documentation; + protected ClassLoader appClassLoader; + protected transient Element element; + protected transient Object[] contributions; + + public ExtensionImpl(ComponentName name, String extensionPoint) { + this(name, extensionPoint, null, null); + } + + public ExtensionImpl(ComponentName name, String extensionPoint, Element element, + ClassLoader appClassLoader) { + this.name = name; + this.extensionPoint = extensionPoint; + this.element = element; + this.appClassLoader = appClassLoader; + } + + public void dispose() { + element = null; + contributions = null; + } + + public Element getElement() { + return element; + } + + public void setElement(Element element) { + this.element = element; + } + + public String getExtensionPoint() { + return extensionPoint; + } + + public ComponentName getComponentName() { + return this.name; + } + + public ComponentName getTargetComponentName() { + return this.target; + } + + public void setTargetComponentName(ComponentName target) { + this.target = target; + } + + public Object[] getContributions() { + return contributions; + } + + @Override + public ClassLoader getAppClassLoader() { + return appClassLoader; + } + + public void setContributions(Object[] contributions) { + this.contributions = contributions; + } + + public String getDocumentation() { + return documentation; + } + + public String getId() { + return null; + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append(ExtensionImpl.class.getSimpleName()); + buf.append(" {"); + buf.append("target: "); + buf.append(name); + buf.append(", point:"); + buf.append(extensionPoint); + buf.append('}'); + return buf.toString(); + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionInternal.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionInternal.java new file mode 100644 index 000000000..af5e8bf12 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionInternal.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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.runtime.api.component.ComponentName; +import com.alipay.sofa.service.api.component.Extension; +import org.w3c.dom.Element; + +/** + * SOFA Extension Internal Object + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ + +public interface ExtensionInternal extends Extension { + + /** + * Set extension element + * + * @param element extension element + */ + void setElement(Element element); + + /** + * Set contributions + * + * @param contribs contributions + */ + void setContributions(Object[] contribs); + + /** + * Set target component(The extension point component) name + * + * @param target target component name。 + */ + void setTargetComponentName(ComponentName target); + + /** + * Dispose the component + */ + void dispose(); + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointComponent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointComponent.java new file mode 100644 index 000000000..20254762b --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointComponent.java @@ -0,0 +1,104 @@ +/* + * 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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.runtime.api.ServiceRuntimeException; +import com.alipay.sofa.runtime.api.component.Property; +import com.alipay.sofa.runtime.model.ComponentType; +import com.alipay.sofa.runtime.spi.component.AbstractComponent; +import com.alipay.sofa.runtime.spi.component.ComponentInfo; +import com.alipay.sofa.runtime.spi.component.ComponentManager; +import com.alipay.sofa.runtime.spi.component.Implementation; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import com.alipay.sofa.runtime.spi.util.ComponentNameFactory; +import com.alipay.sofa.service.api.component.ExtensionPoint; + +import java.util.Map; + +/** + * SOFA ExtensionPoint Component + * + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionPointComponent extends AbstractComponent { + private static final String LINK_SYMBOL = "$"; + public static final ComponentType EXTENSION_POINT_COMPONENT_TYPE = new ComponentType( + "extension-point"); + // Extension + private ExtensionPoint extensionPoint; + + public ExtensionPointComponent(ExtensionPoint extensionPoint, + SofaRuntimeContext sofaRuntimeContext, + Implementation implementation) { + this.extensionPoint = extensionPoint; + this.sofaRuntimeContext = sofaRuntimeContext; + this.implementation = implementation; + this.componentName = ComponentNameFactory.createComponentName( + EXTENSION_POINT_COMPONENT_TYPE, implementation.getName() + LINK_SYMBOL + + this.extensionPoint.getName()); + } + + @Override + public ComponentType getType() { + return EXTENSION_POINT_COMPONENT_TYPE; + } + + @Override + public Map getProperties() { + return null; + } + + @Override + public void activate() throws ServiceRuntimeException { + super.activate(); + + ComponentManager componentManager = sofaRuntimeContext.getComponentManager(); + + for (ComponentInfo componentInfo : componentManager.getComponents()) { + if (componentInfo.getType().equals(ExtensionComponent.EXTENSION_COMPONENT_TYPE) + && !componentInfo.isResolved()) { + ExtensionComponent extensionComponent = (ExtensionComponent) componentInfo; + if (extensionComponent.getExtension().getTargetComponentName() + .equals(componentName)) { + componentManager.resolvePendingResolveComponent(componentInfo.getName()); + } + } + } + } + + @Override + public void deactivate() throws ServiceRuntimeException { + ComponentManager componentManager = sofaRuntimeContext.getComponentManager(); + + for (ComponentInfo componentInfo : componentManager.getComponents()) { + if (componentInfo.getType().equals(ExtensionComponent.EXTENSION_COMPONENT_TYPE)) { + ExtensionComponent extensionComponent = (ExtensionComponent) componentInfo; + if (extensionComponent.getExtension().getTargetComponentName() + .equals(componentName)) { + componentManager.unregister(componentInfo); + } + } + } + super.deactivate(); + } + + public ExtensionPoint getExtensionPoint() { + return extensionPoint; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointImpl.java new file mode 100644 index 000000000..8df40e66f --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointImpl.java @@ -0,0 +1,93 @@ +/* + * 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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.runtime.spi.component.ComponentInfo; +import org.springframework.util.ClassUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * ExtensionPoint Implementation。 + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionPointImpl implements ExtensionPointInternal, Serializable { + private static final long serialVersionUID = 3939941819263075106L; + protected String name; + protected String documentation; + protected transient List> contributions = new ArrayList>(2); + protected transient XMap xmap; + protected ClassLoader beanClassLoader; + + public ExtensionPointImpl(String name, Class contributionClass) { + this.name = name; + if (contributionClass != null) { + this.contributions.add(contributionClass); + } + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + public List> getContributions() { + return contributions; + } + + public boolean hasContribution() { + return contributions.size() > 0; + } + + public String getName() { + return name; + } + + public String getDocumentation() { + return documentation; + } + + public void addContribution(Class javaClass) { + this.contributions.add(javaClass); + } + + public void addContribution(String className) { + this.addContribution(ClassUtils.resolveClassName(className, beanClassLoader)); + } + + public Object[] loadContributions(ExtensionInternal extension) throws Exception { + if (contributions != null) { + if (xmap == null) { + xmap = new XMap(); + for (Class contrib : contributions) { + xmap.register(contrib); + } + } + Object[] contribs = xmap.loadAll(new XMapContext(extension.getAppClassLoader()), + extension.getElement()); + extension.setContributions(contribs); + return contribs; + } + + return null; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointInternal.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointInternal.java new file mode 100644 index 000000000..bccc2b76f --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/ExtensionPointInternal.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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.service.api.component.ExtensionPoint; + +/** + * SOFA Extension Point Internal Object + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public interface ExtensionPointInternal extends ExtensionPoint { + + /** + * load contributions + * + * @param extension extension info + * @return All contributions + * @throws Exception any exception + */ + Object[] loadContributions(ExtensionInternal extension) throws Exception; + + /** + * add contribution + * + * @param className contribution class name + */ + void addContribution(String className); + + /** + * add contribution + * + * @param javaClass contribution class + */ + void addContribution(Class javaClass); + + /** + * set classloader + * + * @param beanClassLoader bean classloader + */ + void setBeanClassLoader(ClassLoader beanClassLoader); +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/XMapContext.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/XMapContext.java new file mode 100644 index 000000000..b462de2f1 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/component/XMapContext.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 com.alipay.sofa.runtime.ext.component; + +import com.alipay.sofa.common.xmap.Context; + +import java.net.URL; + +/** + * @author Bogdan Stefanescu + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class XMapContext extends Context { + + private static final long serialVersionUID = -7194560385886298218L; + + private ClassLoader appClassLoader; + + /** + * + * @param appClassLoader + */ + public XMapContext(ClassLoader appClassLoader) { + this.appClassLoader = appClassLoader; + } + + /** + * + * @param className + * @return + * @throws ClassNotFoundException + */ + @Override + public Class loadClass(String className) throws ClassNotFoundException { + return appClassLoader.loadClass(className); + } + + /** + * + * @param name + * @return + */ + @Override + public URL getResource(String name) { + return appClassLoader.getResource(name); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/AbstractExtFactoryBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/AbstractExtFactoryBean.java new file mode 100644 index 000000000..b5b209993 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/AbstractExtFactoryBean.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 com.alipay.sofa.runtime.ext.spring; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.Ordered; + +/** + * Common bean for extension and extension point + * + * @author yangyanzhao@alipay.com + * @since 2.6.0 + */ +public class AbstractExtFactoryBean extends CommonContextBean implements BeanFactoryAware, + FactoryBean, Ordered { + /* Spring bean context for looking up spring's bean */ + protected BeanFactory beanFactory; + + protected String targetBeanName; + + protected Object target; + + public final static String LINK_SYMBOL = "$"; + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + + } + + /** + * no real bean exist + * @return null + * @throws Exception any exception + */ + public Object getObject() throws Exception { + return null; + } + + /** + * no real bean exist + * @return String.class + */ + public Class getObjectType() { + return String.class; + } + + public boolean isSingleton() { + return true; + } + + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + /** + * Export the given object as an OSGi service. Normally used when the + * exported service is a nested bean or an object not managed by the Spring + * container. + * + * @param target The target to set. + */ + public void setTarget(Object target) { + this.target = target; + } + + public void setTargetBeanName(String name) { + this.targetBeanName = name; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/CommonContextBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/CommonContextBean.java new file mode 100644 index 000000000..45e4e17a2 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/CommonContextBean.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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.runtime.constants.SofaRuntimeFrameworkConstants; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * + * @author linfengqi + * @since 2.6.0 + */ +public class CommonContextBean implements ApplicationContextAware, BeanNameAware, InitializingBean { + + protected String beanName; + protected ClassLoader beanClassLoader; + protected ConfigurableListableBeanFactory configurableListableBeanFactory; + protected SofaRuntimeContext sofaRuntimeContext; + protected ApplicationContext applicationContext; + + @Override + public void afterPropertiesSet() throws Exception { + sofaRuntimeContext = applicationContext.getBean( + SofaRuntimeFrameworkConstants.SOFA_RUNTIME_CONTEXT_BEAN_ID, SofaRuntimeContext.class); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + configurableListableBeanFactory = (ConfigurableListableBeanFactory) applicationContext + .getAutowireCapableBeanFactory(); + } + + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + public ClassLoader getBeanClassLoader() { + return beanClassLoader; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionBuilder.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionBuilder.java new file mode 100644 index 000000000..ca0bad791 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionBuilder.java @@ -0,0 +1,83 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.runtime.api.component.ComponentName; +import com.alipay.sofa.runtime.ext.component.ExtensionInternal; +import com.alipay.sofa.service.api.component.Extension; +import org.springframework.context.ApplicationContext; +import org.w3c.dom.Element; + +/** + * Extension Builder + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ + +public class ExtensionBuilder { + + private ExtensionInternal extensionInternal; + + private ExtensionBuilder() { + + } + + /** + * Get extension + * @return extension + */ + public Extension getExtension() { + return this.extensionInternal; + } + + /** + * create extension builder + * + * @param extensionPoint extension point name + * @param element element + * @param applicationContext application context + * @return extension builder + */ + public static ExtensionBuilder genericExtension(String extensionPoint, Element element, + ApplicationContext applicationContext, + ClassLoader appClassLoader) { + ExtensionBuilder builder = new ExtensionBuilder(); + builder.extensionInternal = new SpringExtensionImpl(null, extensionPoint, element, + appClassLoader, applicationContext); + return builder; + } + + /** + * Set element + * + * @param element element of the extension + */ + public void setElement(Element element) { + this.extensionInternal.setElement(element); + } + + /** + * Set target component name + * + * @param target target component name + */ + public void setExtensionPoint(ComponentName target) { + this.extensionInternal.setTargetComponentName(target); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionFactoryBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionFactoryBean.java new file mode 100644 index 000000000..f96894519 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionFactoryBean.java @@ -0,0 +1,135 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.runtime.api.component.ComponentName; +import com.alipay.sofa.runtime.ext.component.ExtensionComponent; +import com.alipay.sofa.runtime.ext.component.ExtensionPointComponent; +import com.alipay.sofa.runtime.spi.component.ComponentInfo; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import com.alipay.sofa.runtime.spi.util.ComponentNameFactory; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * + * @author xi.hux@alipay.com + * @author yangyanzhao@alipay.com + * @author khotyn + * @since 2.6.0 + */ +public class ExtensionFactoryBean extends AbstractExtFactoryBean { + + /* extension bean */ + private String bean; + + /* extension point name */ + private String point; + + /* content need to be parsed with XMap */ + private Element content; + + private String[] require; + + private ClassLoader classLoader; + + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.notNull(applicationContext, + "required property 'applicationContext' has not been set"); + Assert.notNull(getPoint(), "required property 'point' has not been set for extension"); + // checked + if (!StringUtils.hasText(getPoint())) { + throw new IllegalArgumentException("'point' have to be specified"); + } + + if (classLoader == null) { + classLoader = Thread.currentThread().getContextClassLoader(); + } + + try { + publishAsNuxeoExtension(); + } catch (Exception e) { + SofaLogger.error(e, "failed to publish extension"); + throw e; + } + } + + private void publishAsNuxeoExtension() throws Exception { + ExtensionBuilder extensionBuilder = ExtensionBuilder.genericExtension(this.getPoint(), + this.getContent(), applicationContext, classLoader); + extensionBuilder.setExtensionPoint(getExtensionPointComponentName()); + + ComponentInfo componentInfo = new ExtensionComponent(extensionBuilder.getExtension(), + sofaRuntimeContext); + sofaRuntimeContext.getComponentManager().register(componentInfo); + } + + private ComponentName getExtensionPointComponentName() { + return ComponentNameFactory.createComponentName( + ExtensionPointComponent.EXTENSION_POINT_COMPONENT_TYPE, this.getBean() + LINK_SYMBOL + + this.getPoint()); + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public void setContribution(String[] contribution) throws Exception { + if (contribution != null && contribution.length != 0) { + Class[] contrClass = new Class[contribution.length]; + int i = 0; + for (String cntr : contribution) { + contrClass[i] = classLoader.loadClass(cntr); + i++; + } + } + } + + public void setRequire(String[] require) { + this.require = require; + } + + public String[] getRequire() { + return require; + } + + public void setBean(String bean) { + this.bean = bean; + } + + public String getBean() { + return bean; + } + + public void setContent(Element content) { + this.content = content; + } + + public Element getContent() { + return content; + } + + public void setPoint(String point) { + this.point = point; + } + + public String getPoint() { + return point; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionPointBuilder.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionPointBuilder.java new file mode 100644 index 000000000..a2b6e04f9 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionPointBuilder.java @@ -0,0 +1,91 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.runtime.ext.component.ExtensionPointInternal; +import com.alipay.sofa.service.api.component.ExtensionPoint; + +/** + * Extension point builder + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class ExtensionPointBuilder { + + private ExtensionPointInternal extensionPointInternal; + + private ExtensionPointBuilder() { + + } + + /** + * Get extension point + * @return extension point + */ + public ExtensionPoint getExtensionPoint() { + return this.extensionPointInternal; + } + + /** + * Create extension point builder + * @param name extension point name + * @param beanClassLoader classloader + * @return extension point builder + */ + public static ExtensionPointBuilder genericExtensionPoint(String name, + ClassLoader beanClassLoader) { + return genericExtensionPoint(name, null, beanClassLoader); + } + + /** + * Create extension point builder + * @param name extension point name + * @param contributionClass contribution class + * @param beanClassLoader classloader + * @return extension point builder + */ + public static ExtensionPointBuilder genericExtensionPoint(String name, + Class contributionClass, + ClassLoader beanClassLoader) { + ExtensionPointBuilder builder = new ExtensionPointBuilder(); + + ExtensionPointInternal extensionPoint = new SpringExtensionPointImpl(name, + contributionClass); + extensionPoint.setBeanClassLoader(beanClassLoader); + + builder.extensionPointInternal = extensionPoint; + return builder; + } + + /** + * add contribution to extension point + * @param javaClass contribution class + */ + public void addContribution(Class javaClass) { + this.extensionPointInternal.addContribution(javaClass); + } + + /** + * add contribution to extension point + * @param className contribution class name + */ + public void addContribution(String className) { + this.extensionPointInternal.addContribution(className); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionPointFactoryBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionPointFactoryBean.java new file mode 100644 index 000000000..532d43036 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/ExtensionPointFactoryBean.java @@ -0,0 +1,120 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.runtime.ext.component.ExtensionPointComponent; +import com.alipay.sofa.runtime.spi.component.ComponentInfo; +import com.alipay.sofa.runtime.spi.component.Implementation; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import com.alipay.sofa.runtime.spi.spring.SpringImplementationImpl; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Extension point factory bean + * + * @author xi.hux@alipay.com + * @author yangyanzhao@alipay.com + * @since 2.6.0 + */ +public class ExtensionPointFactoryBean extends AbstractExtFactoryBean { + + /* extension point name */ + private String name; + + /* contributions for the extension point */ + private String[] contribution; + + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.notNull(beanFactory, "required property 'beanFactory' has not been set"); + Assert.notNull(name, "required property 'name' has not been set for extension point"); + + if (!StringUtils.hasText(targetBeanName) && target == null + || (StringUtils.hasText(targetBeanName) && target != null)) { + throw new IllegalArgumentException( + "either 'target' or 'targetBeanName' have to be specified"); + } + + // determine serviceClass (can still be null if using a FactoryBean + // which doesn't declare its product type) + Class extensionPointClass = (target != null ? target.getClass() : beanFactory + .getType(targetBeanName)); + + // check if there is a reference to a non-lazy bean + if (StringUtils.hasText(targetBeanName)) { + if (beanFactory instanceof ConfigurableListableBeanFactory) { + // in case the target is non-lazy, singleton bean, initialize it + BeanDefinition beanDef = ((ConfigurableListableBeanFactory) beanFactory) + .getBeanDefinition(targetBeanName); + + if (beanDef.isSingleton() && !beanDef.isLazyInit()) { + if (SofaLogger.isDebugEnabled()) { + SofaLogger + .debug("target bean [" + + targetBeanName + + "] is a non-lazy singleton; forcing initialization before publishing"); + } + beanFactory.getBean(targetBeanName); + } + } + } + + try { + publishAsNuxeoExtensionPoint(extensionPointClass); + } catch (Exception e) { + SofaLogger.error(e, "Failed to publish extension point."); + throw e; + } + } + + private void publishAsNuxeoExtensionPoint(Class beanClass) throws Exception { + Assert.notNull(beanClass, "Service must be implement!"); + + ExtensionPointBuilder extensionPointBuilder = ExtensionPointBuilder.genericExtensionPoint( + this.name, applicationContext.getClassLoader()); + + if (this.contribution != null && this.contribution.length != 0) { + for (int i = 0; i < contribution.length; i++) { + extensionPointBuilder.addContribution(contribution[i]); + } + } + Assert.hasLength(beanName, + "required property 'beanName' has not been set for creating implementation"); + Assert.notNull(applicationContext, + "required property 'applicationContext' has not been set for creating implementation"); + Implementation implementation = new SpringImplementationImpl(targetBeanName, + applicationContext); + ComponentInfo extensionPointComponent = new ExtensionPointComponent( + extensionPointBuilder.getExtensionPoint(), sofaRuntimeContext, implementation); + sofaRuntimeContext.getComponentManager().register(extensionPointComponent); + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setContribution(String[] contribution) throws Exception { + this.contribution = contribution; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/SpringExtensionImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/SpringExtensionImpl.java new file mode 100644 index 000000000..fdf58dc3b --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/SpringExtensionImpl.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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.runtime.api.component.ComponentName; +import com.alipay.sofa.runtime.ext.component.ExtensionImpl; +import org.springframework.context.ApplicationContext; +import org.w3c.dom.Element; + +/** + * Extension implement in spring env + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ + +public class SpringExtensionImpl extends ExtensionImpl { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1574173210970111642L; + + protected ApplicationContext applicationContext; + + public SpringExtensionImpl(ComponentName name, String extensionPoint, Element element, + ClassLoader appClassLoader, ApplicationContext applicationContext) { + super(name, extensionPoint, element, appClassLoader); + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return this.applicationContext; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/SpringExtensionPointImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/SpringExtensionPointImpl.java new file mode 100644 index 000000000..c29b29b9a --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/SpringExtensionPointImpl.java @@ -0,0 +1,72 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring; + +import com.alipay.sofa.common.xmap.annotation.spring.XMapSpring; +import com.alipay.sofa.runtime.ext.component.ExtensionInternal; +import com.alipay.sofa.runtime.ext.component.ExtensionPointImpl; +import com.alipay.sofa.runtime.ext.component.XMapContext; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.context.ApplicationContext; + +/** + * Extension point implement in spring env + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ + +public class SpringExtensionPointImpl extends ExtensionPointImpl { + + private static final long serialVersionUID = -7891847787889605861L; + + private XMapSpring xmapSpring; + + public SpringExtensionPointImpl(String name, Class contributionClass) { + super(name, contributionClass); + } + + @Override + public Object[] loadContributions(ExtensionInternal extension) throws Exception { + + ApplicationContext applicationContext = null; + + if (extension instanceof SpringExtensionImpl) { + applicationContext = ((SpringExtensionImpl) extension).getApplicationContext(); + } + if (contributions != null) { + xmapSpring = new XMapSpring(); + for (Class contrib : contributions) { + xmapSpring.register(contrib, applicationContext); + } + + Object[] contribs = xmapSpring.loadAll(new XMapContext(extension.getAppClassLoader()), + extension.getElement()); + for (Object o : contribs) { + if (applicationContext != null && o instanceof BeanFactoryAware) { + ((BeanFactoryAware) o).setBeanFactory(applicationContext + .getAutowireCapableBeanFactory()); + } + } + extension.setContributions(contribs); + + return contribs; + } + return null; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractExtBeanDefinitionParser.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractExtBeanDefinitionParser.java new file mode 100644 index 000000000..1bb1c118b --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractExtBeanDefinitionParser.java @@ -0,0 +1,97 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring.parser; + +import com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport; +import com.alipay.sofa.runtime.ext.spring.parser.AbstractSingleExtPointBeanDefinitionParser; +import com.alipay.sofa.runtime.spi.util.ParserUtils; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.springframework.core.io.Resource; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; + +/** + * Common parser for extension and extension point + * + * @author yangyanzhao@alipay.com + * @author yangyanzhao + * @since 2.6.0 + */ +public abstract class AbstractExtBeanDefinitionParser extends + AbstractSingleExtPointBeanDefinitionParser + implements + SofaBootTagNameSupport { + public static final String REF = "ref"; + + private static final String BEAN_CLASS_LOADER = "beanClassLoader"; + + /** + * + * @param element the XML element being parsed + * @param parserContext the object encapsulating the current state of the parsing process + * @param builder used to define the BeanDefinition + */ + @Override + protected void doParse(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + Resource res = parserContext.getReaderContext().getResource(); + + builder.getBeanDefinition().setResource(res); + builder.getRawBeanDefinition().setResource(res); + + configBeanClassLoader(parserContext, builder); + + // parse attributes + parseAttribute(element, parserContext, builder); + + // parser subElement(i.e. ) + parserSubElement(element, parserContext, builder); + } + + protected void configBeanClassLoader(ParserContext parserContext, BeanDefinitionBuilder builder) { + ClassLoader beanClassLoader = parserContext.getReaderContext().getBeanClassLoader(); + builder.addPropertyValue(BEAN_CLASS_LOADER, beanClassLoader); + } + + protected void parseAttribute(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + ParserUtils.parseCustomAttributes(element, parserContext, builder, + new ParserUtils.AttributeCallback() { + + public void process(Element parent, Attr attribute, BeanDefinitionBuilder builder, + ParserContext parserContext) { + String name = attribute.getLocalName(); + + // fallback mechanism + if (!REF.equals(name)) { + builder.addPropertyValue(Conventions.attributeNameToPropertyName(name), + attribute.getValue()); + } + + } + }); + } + + protected abstract void parserSubElement(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder); + + protected boolean shouldGenerateIdAsFallback() { + return true; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractExtPointBeanDefinitionParser.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractExtPointBeanDefinitionParser.java new file mode 100644 index 000000000..577a99d1e --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractExtPointBeanDefinitionParser.java @@ -0,0 +1,193 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring.parser; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * @author fengqi.lin + * @since 2.6.0 + */ +public abstract class AbstractExtPointBeanDefinitionParser implements BeanDefinitionParser { + + /** + * Constant for the id attribute + */ + public static final String ID_ATTRIBUTE = "id"; + + /** + * + * @param element + * @param parserContext + * @return + */ + public final BeanDefinition parse(Element element, ParserContext parserContext) { + AbstractBeanDefinition definition = parseInternal(element, parserContext); + if (definition != null && !parserContext.isNested()) { + try { + String id = resolveId(element, definition, parserContext); + if (!StringUtils.hasText(id)) { + parserContext.getReaderContext().error( + "Id is required for element '" + + parserContext.getDelegate().getLocalName(element) + + "' when used as a top-level tag", element); + } + BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id); + registerBeanDefinition(holder, parserContext.getRegistry()); + if (shouldFireEvents()) { + BeanComponentDefinition componentDefinition = new BeanComponentDefinition( + holder); + postProcessComponentDefinition(componentDefinition); + parserContext.registerComponent(componentDefinition); + } + } catch (BeanDefinitionStoreException ex) { + parserContext.getReaderContext().error(ex.getMessage(), element); + return null; + } + } + return definition; + } + + /** + * Resolve the ID for the supplied {@link BeanDefinition}. + *

When using {@link #shouldGenerateId generation}, a name is generated automatically. + * Otherwise, the ID is extracted from the "id" attribute, potentially with a + * {@link #shouldGenerateIdAsFallback() fallback} to a generated id. + * + * @param element the element that the bean definition has been built from + * @param definition the bean definition to be registered + * @param parserContext the object encapsulating the current state of the parsing process; + * provides access to a {@link BeanDefinitionRegistry} + * @return the resolved id + * @throws BeanDefinitionStoreException if no unique name could be generated + * for the given bean definition + */ + protected String resolveId(Element element, AbstractBeanDefinition definition, + ParserContext parserContext) throws BeanDefinitionStoreException { + + if (shouldGenerateId()) { + return parserContext.getReaderContext().generateBeanName(definition); + } else { + String id = element.getAttribute(ID_ATTRIBUTE); + if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) { + id = parserContext.getReaderContext().generateBeanName(definition); + } + return id; + } + } + + /** + * Register the supplied {@link BeanDefinitionHolder bean} with the supplied + * {@link BeanDefinitionRegistry registry}. + *

Subclasses can override this method to control whether or not the supplied + * {@link BeanDefinitionHolder bean} is actually even registered, or to + * register even more beans. + *

The default implementation registers the supplied {@link BeanDefinitionHolder bean} + * with the supplied {@link BeanDefinitionRegistry registry} only if the isNested + * parameter is false, because one typically does not want inner beans + * to be registered as top level beans. + * + * @param definition the bean definition to be registered + * @param registry the registry that the bean is to be registered with + * @see BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder, BeanDefinitionRegistry) + */ + protected void registerBeanDefinition(BeanDefinitionHolder definition, + BeanDefinitionRegistry registry) { + BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry); + } + + /** + * Central template method to actually parse the supplied {@link Element} + * into one or more {@link BeanDefinition BeanDefinitions}. + * + * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} + * @param parserContext the object encapsulating the current state of the parsing process; + * provides access to a {@link BeanDefinitionRegistry} + * @return the primary {@link BeanDefinition} resulting from the parsing of the supplied {@link Element} + * @see #parse(Element, ParserContext) + * @see #postProcessComponentDefinition(BeanComponentDefinition) + */ + protected abstract AbstractBeanDefinition parseInternal(Element element, + ParserContext parserContext); + + /** + * Should an ID be generated instead of read from the passed in {@link Element}? + *

Disabled by default; subclasses can override this to enable ID generation. + * Note that this flag is about always generating an ID; the parser + * won't even check for an "id" attribute in this case. + * + * @return whether the parser should always generate an id + */ + protected boolean shouldGenerateId() { + return false; + } + + /** + * Should an ID be generated instead if the passed in {@link Element} does not + * specify an "id" attribute explicitly? + *

Disabled by default; subclasses can override this to enable ID generation + * as fallback: The parser will first check for an "id" attribute in this case, + * only falling back to a generated ID if no value was specified. + * + * @return whether the parser should generate an id if no id was specified + */ + protected boolean shouldGenerateIdAsFallback() { + return false; + } + + /** + * Controls whether this parser is supposed to fire a + * {@link BeanComponentDefinition} + * event after parsing the bean definition. + *

This implementation returns true by default; that is, + * an event will be fired when a bean definition has been completely parsed. + * Override this to return false in order to suppress the event. + * + * @return true in order to fire a component registration event + * after parsing the bean definition; false to suppress the event + * @see #postProcessComponentDefinition + * @see org.springframework.beans.factory.parsing.ReaderContext#fireComponentRegistered + */ + protected boolean shouldFireEvents() { + return true; + } + + /** + * Hook method called after the primary parsing of a + * {@link BeanComponentDefinition} but before the + * {@link BeanComponentDefinition} has been registered with a + * {@link BeanDefinitionRegistry}. + *

Derived classes can override this method to supply any custom logic that + * is to be executed after all the parsing is finished. + *

The default implementation is a no-op. + * + * @param componentDefinition the {@link BeanComponentDefinition} that is to be processed + */ + protected void postProcessComponentDefinition(BeanComponentDefinition componentDefinition) { + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractSingleExtPointBeanDefinitionParser.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractSingleExtPointBeanDefinitionParser.java new file mode 100644 index 000000000..497530abf --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/AbstractSingleExtPointBeanDefinitionParser.java @@ -0,0 +1,143 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring.parser; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * @author fengqi.lin + * @since 2.6.0 + */ +public abstract class AbstractSingleExtPointBeanDefinitionParser extends + AbstractExtPointBeanDefinitionParser { + + /** + * Creates a {@link BeanDefinitionBuilder} instance for the + * {@link #getBeanClass bean Class} and passes it to the + * {@link #doParse} strategy method. + * + * @param element the element that is to be parsed into a single BeanDefinition + * @param parserContext the object encapsulating the current state of the parsing process + * @return the BeanDefinition resulting from the parsing of the supplied {@link Element} + * @throws IllegalStateException if the bean {@link Class} returned from + * {@link #getBeanClass(Element)} is null + * @see #doParse + */ + @Override + protected final AbstractBeanDefinition parseInternal(Element element, + ParserContext parserContext) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); + String parentName = getParentName(element); + if (parentName != null) { + builder.getRawBeanDefinition().setParentName(parentName); + } + Class beanClass = getBeanClass(element); + if (beanClass != null) { + builder.getRawBeanDefinition().setBeanClass(beanClass); + } else { + String beanClassName = getBeanClassName(element); + if (beanClassName != null) { + builder.getRawBeanDefinition().setBeanClassName(beanClassName); + } + } + builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); + if (parserContext.isNested()) { + // Inner bean definition must receive same scope as containing bean. + builder.setScope(parserContext.getContainingBeanDefinition().getScope()); + } + if (parserContext.isDefaultLazyInit()) { + // Default-lazy-init applies to custom bean definitions as well. + builder.setLazyInit(true); + } + doParse(element, parserContext, builder); + return builder.getBeanDefinition(); + } + + /** + * Determine the name for the parent of the currently parsed bean, + * in case of the current bean being defined as a child bean. + *

The default implementation returns null, + * indicating a root bean definition. + * + * @param element the Element that is being parsed + * @return the name of the parent bean for the currently parsed bean, + * or null if none + */ + protected String getParentName(Element element) { + return null; + } + + /** + * Determine the bean class corresponding to the supplied {@link Element}. + *

Note that, for application classes, it is generally preferable to + * override {@link #getBeanClassName} instead, in order to avoid a direct + * dependence on the bean implementation class. The BeanDefinitionParser + * and its NamespaceHandler can be used within an IDE plugin then, even + * if the application classes are not available on the plugin's classpath. + * + * @param element the Element that is being parsed + * @return the {@link Class} of the bean that is being defined via parsing + * the supplied Element, or null if none + * @see #getBeanClassName + */ + protected Class getBeanClass(Element element) { + return null; + } + + /** + * Determine the bean class name corresponding to the supplied {@link Element}. + * + * @param element the Element that is being parsed + * @return the class name of the bean that is being defined via parsing + * the supplied Element, or null if none + * @see #getBeanClass + */ + protected String getBeanClassName(Element element) { + return null; + } + + /** + * Parse the supplied {@link Element} and populate the supplied + * {@link BeanDefinitionBuilder} as required. + *

The default implementation delegates to the doParse + * version without ParserContext argument. + * + * @param element the XML element being parsed + * @param parserContext the object encapsulating the current state of the parsing process + * @param builder used to define the BeanDefinition + * @see #doParse(Element, BeanDefinitionBuilder) + */ + protected void doParse(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + doParse(element, builder); + } + + /** + * Parse the supplied {@link Element} and populate the supplied + * {@link BeanDefinitionBuilder} as required. + *

The default implementation does nothing. + * + * @param element the XML element being parsed + * @param builder used to define the BeanDefinition + */ + protected void doParse(Element element, BeanDefinitionBuilder builder) { + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/ExtensionBeanDefinitionParser.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/ExtensionBeanDefinitionParser.java new file mode 100644 index 000000000..a685ac2f6 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/ExtensionBeanDefinitionParser.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 com.alipay.sofa.runtime.ext.spring.parser; + +import com.alipay.sofa.runtime.ext.spring.ExtensionFactoryBean; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Extension definition parser + * + * @author xi.hux@alipay.com + * @author yangyanzhao@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionBeanDefinitionParser extends AbstractExtBeanDefinitionParser { + + public static final String CONTENT = "content"; + + private static final ExtensionBeanDefinitionParser instance = new ExtensionBeanDefinitionParser(); + + public ExtensionBeanDefinitionParser() { + } + + public static ExtensionBeanDefinitionParser getInstance() { + return instance; + } + + @Override + protected Class getBeanClass(Element element) { + return ExtensionFactoryBean.class; + } + + @Override + protected void parserSubElement(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + NodeList nl = element.getChildNodes(); + + // parse all sub elements + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element) { + Element subElement = (Element) node; + // osgi:content + if (CONTENT.equals(subElement.getLocalName())) { + builder.addPropertyValue(CONTENT, subElement); + } + } + } + } + + @Override + public String supportTagName() { + return "extension"; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/ExtensionPointBeanDefinitionParser.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/ExtensionPointBeanDefinitionParser.java new file mode 100644 index 000000000..52e1d4d91 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/ext/spring/parser/ExtensionPointBeanDefinitionParser.java @@ -0,0 +1,126 @@ +/* + * 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 com.alipay.sofa.runtime.ext.spring.parser; + +import com.alipay.sofa.runtime.ext.spring.ExtensionPointFactoryBean; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import com.alipay.sofa.runtime.spi.util.ParserUtils; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.Conventions; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Extension point definition parser + * + * @author xi.hux@alipay.com + * @author yangyanzhao@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionPointBeanDefinitionParser extends AbstractExtBeanDefinitionParser { + + public static final String CLASS = "class"; + + public static final String OBJECT = "object"; + + public static final String CONTRIBUTION = "contribution"; + + private static final ExtensionPointBeanDefinitionParser instance = new ExtensionPointBeanDefinitionParser(); + + public ExtensionPointBeanDefinitionParser() { + } + + public static ExtensionPointBeanDefinitionParser getInstance() { + return instance; + } + + @Override + protected Class getBeanClass(Element element) { + return ExtensionPointFactoryBean.class; + } + + @Override + protected void parserSubElement(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder) { + // determine nested/referred beans + // only referred bean is supported now + Object target = null; + if (element.hasAttribute(REF)) { + target = new RuntimeBeanReference(element.getAttribute(REF)); + } + + NodeList nl = element.getChildNodes(); + final Set contributions = new LinkedHashSet(); + // parse all sub elements + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element) { + Element subElement = (Element) node; + // sofa:object + if (OBJECT.equals(subElement.getLocalName())) { + ParserUtils.parseCustomAttributes(subElement, parserContext, builder, + new ParserUtils.AttributeCallback() { + + public void process(Element parent, Attr attribute, + BeanDefinitionBuilder builder, + ParserContext parserContext) { + + String name = attribute.getLocalName(); + if (CLASS.equals(name)) { + contributions.add(attribute.getValue()); + } else { + builder.addPropertyValue( + Conventions.attributeNameToPropertyName(name), + attribute.getValue()); + } + } + }); + } else { + if (element.hasAttribute(REF)) { + SofaLogger + .error("nested bean definition/reference cannot be used when attribute 'ref' is specified"); + } + target = parserContext.getDelegate().parsePropertySubElement(subElement, + builder.getBeanDefinition()); + } + } + } + builder.addPropertyValue(CONTRIBUTION, contributions); + // do we have a bean reference ? + if (target instanceof RuntimeBeanReference) { + builder.addPropertyValue("targetBeanName", + ((RuntimeBeanReference) target).getBeanName()); + } + // or a nested bean? -- not supported yet + else { + builder.addPropertyValue("target", target); + } + } + + @Override + public String supportTagName() { + return "extension-point"; + } +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/AbstractComponent.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/AbstractComponent.java index bc137ab21..ccf83d35a 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/AbstractComponent.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/AbstractComponent.java @@ -17,6 +17,7 @@ package com.alipay.sofa.runtime.spi.component; import com.alipay.sofa.runtime.api.ServiceRuntimeException; +import com.alipay.sofa.runtime.api.component.ComponentLifeCycle; import com.alipay.sofa.runtime.api.component.ComponentName; import com.alipay.sofa.runtime.model.ComponentStatus; import com.alipay.sofa.runtime.spi.health.HealthResult; @@ -121,6 +122,13 @@ public void activate() throws ServiceRuntimeException { return; } + if (this.implementation != null) { + Object target = this.implementation.getTarget(); + if (target instanceof ComponentLifeCycle) { + ((ComponentLifeCycle) target).activate(); + } + } + componentStatus = ComponentStatus.ACTIVATED; } @@ -134,6 +142,13 @@ public void deactivate() throws ServiceRuntimeException { return; } + if (this.implementation != null) { + Object target = this.implementation.getTarget(); + if (target instanceof ComponentLifeCycle) { + ((ComponentLifeCycle) target).deactivate(); + } + } + componentStatus = ComponentStatus.RESOLVED; } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/ComponentManager.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/ComponentManager.java index 15a96e7df..96fdd8cf4 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/ComponentManager.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/component/ComponentManager.java @@ -106,4 +106,11 @@ public interface ComponentManager { * @return components */ Collection getComponentInfosByType(ComponentType type); + + /** + * resolve pending component + * + * @param componentName component name + */ + void resolvePendingResolveComponent(ComponentName componentName); } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/log/SofaLogger.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/log/SofaLogger.java index 2875c4304..1bc3fcb17 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/log/SofaLogger.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/log/SofaLogger.java @@ -45,6 +45,10 @@ public static void warn(String format, Object... args) { } } + public static void error(String msg, Throwable throwable) { + DEFAULT_LOG.error(msg, throwable); + } + public static void error(String format, Object... args) { DEFAULT_LOG.error(getMessage(format, args)); } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/spring/SpringImplementationImpl.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/spring/SpringImplementationImpl.java new file mode 100644 index 000000000..4d788d5f6 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/spring/SpringImplementationImpl.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 com.alipay.sofa.runtime.spi.spring; + +import com.alipay.sofa.runtime.spi.component.DefaultImplementation; +import org.springframework.context.ApplicationContext; +import org.springframework.util.Assert; + +/** + * Spring Component Implement + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ + +public class SpringImplementationImpl extends DefaultImplementation { + + protected ApplicationContext applicationContext; + protected String beanName; + protected Object target; + + public SpringImplementationImpl(String beanName, ApplicationContext applicationContext) { + Assert.hasText(beanName, "beanName must not be empty"); + Assert.notNull(applicationContext, "applicationContext must not be null"); + + this.beanName = beanName; + this.applicationContext = applicationContext; + } + + @Override + public Object getTarget() { + return applicationContext.getBean(this.beanName); + } + + @Override + public Class getTargetClass() { + return applicationContext.getBean(this.beanName).getClass(); + } + + @Override + public void setTarget(Object target) { + this.target = target; + } + + @Override + public String getName() { + return beanName; + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/util/ParserUtils.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/util/ParserUtils.java new file mode 100644 index 000000000..b5ebfeae2 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spi/util/ParserUtils.java @@ -0,0 +1,86 @@ +/* + * 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 com.alipay.sofa.runtime.spi.util; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.util.StringUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; + +/** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ +public class ParserUtils { + + /** + * Parse custom attributes, as ID, LAZY-INIT, DEPENDS-ON。 + * + * @param element element + * @param parserContext parser context + * @param builder builder + * @param callback callback + */ + public static void parseCustomAttributes(Element element, ParserContext parserContext, + BeanDefinitionBuilder builder, + AttributeCallback callback) { + NamedNodeMap attributes = element.getAttributes(); + + for (int x = 0; x < attributes.getLength(); x++) { + Attr attribute = (Attr) attributes.item(x); + String name = attribute.getLocalName(); + + if (BeanDefinitionParserDelegate.DEPENDS_ON_ATTRIBUTE.equals(name)) { + builder.getBeanDefinition().setDependsOn( + (StringUtils.tokenizeToStringArray(attribute.getValue(), + BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS))); + } else if (BeanDefinitionParserDelegate.LAZY_INIT_ATTRIBUTE.equals(name)) { + builder.setLazyInit(Boolean.getBoolean(attribute.getValue())); + } else if (BeanDefinitionParserDelegate.ABSTRACT_ATTRIBUTE.equals(name)) { + builder.setAbstract(Boolean.parseBoolean(attribute.getValue())); + } else if (BeanDefinitionParserDelegate.PARENT_ATTRIBUTE.equals(name)) { + builder.setParentName(attribute.getValue()); + } else { + callback.process(element, attribute, builder, parserContext); + } + } + } + + /** + * + * @author xi.hux@alipay.com + * @since 2.6.0 + */ + public interface AttributeCallback { + + /** + * Parser attribute + * + * @param parent element parent + * @param attribute attribute + * @param builder builder + * @param parserContext parser context + */ + void process(Element parent, Attr attribute, BeanDefinitionBuilder builder, + ParserContext parserContext); + } + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ApplicationShutdownCallbackPostProcessor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ApplicationShutdownCallbackPostProcessor.java index 6f33467b4..92475728d 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ApplicationShutdownCallbackPostProcessor.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ApplicationShutdownCallbackPostProcessor.java @@ -19,14 +19,17 @@ import com.alipay.sofa.runtime.api.event.ApplicationShutdownCallback; import com.alipay.sofa.runtime.spi.component.SofaRuntimeManager; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.PriorityOrdered; /** * @author xuanbei * @author qilong.zql * @since 2.5.0 */ -public class ApplicationShutdownCallbackPostProcessor implements BeanPostProcessor { +public class ApplicationShutdownCallbackPostProcessor implements MergedBeanDefinitionPostProcessor, + PriorityOrdered { private SofaRuntimeManager sofaRuntimeManager; @@ -34,6 +37,12 @@ public ApplicationShutdownCallbackPostProcessor(SofaRuntimeManager sofaRuntimeMa this.sofaRuntimeManager = sofaRuntimeManager; } + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, + Class beanType, String beanName) { + // no process + } + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { @@ -48,4 +57,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) } return bean; } + + @Override + public int getOrder() { + return PriorityOrdered.LOWEST_PRECEDENCE; + } } \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/AsyncProxyBeanPostProcessor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/AsyncProxyBeanPostProcessor.java new file mode 100644 index 000000000..68f25bc2a --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/AsyncProxyBeanPostProcessor.java @@ -0,0 +1,149 @@ +/* + * 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 com.alipay.sofa.runtime.spring; + +import com.alipay.sofa.infra.constants.SofaBootInfraConstants; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import com.alipay.sofa.runtime.spring.async.AsyncInitBeanHolder; +import com.alipay.sofa.runtime.spring.async.AsyncTaskExecutor; +import com.alipay.sofa.runtime.spring.parser.AsyncInitBeanDefinitionDecorator; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.support.AbstractApplicationContext; + +import java.lang.reflect.Method; +import java.util.concurrent.CountDownLatch; + +/** + * @author qilong.zql + * @author xuanbei + * @since 2.6.0 + */ +public class AsyncProxyBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, + InitializingBean { + + private ApplicationContext applicationContext; + + private String moduleName; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + String methodName = AsyncInitBeanHolder.getAsyncInitMethodName(moduleName, beanName); + if (methodName == null || methodName.length() == 0) { + return bean; + } + + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.setTargetClass(bean.getClass()); + proxyFactory.setProxyTargetClass(true); + AsyncInitializeBeanMethodInvoker asyncInitializeBeanMethodInvoker = new AsyncInitializeBeanMethodInvoker( + bean, beanName, methodName); + proxyFactory.addAdvice(asyncInitializeBeanMethodInvoker); + return proxyFactory.getProxy(); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public void afterPropertiesSet() { + ConfigurableBeanFactory beanFactory = ((AbstractApplicationContext) applicationContext) + .getBeanFactory(); + if (AsyncInitBeanDefinitionDecorator.isBeanLoadCostBeanFactory(beanFactory.getClass())) { + moduleName = AsyncInitBeanDefinitionDecorator.getModuleNameFromBeanFactory(beanFactory); + } else { + moduleName = SofaBootInfraConstants.ROOT_APPLICATION_CONTEXT; + } + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + class AsyncInitializeBeanMethodInvoker implements MethodInterceptor { + private final Object targetObject; + private final String asyncMethodName; + private final String beanName; + private final CountDownLatch initCountDownLatch = new CountDownLatch(1); + // mark async-init method is during first invocation. + private volatile boolean isAsyncCalling = false; + // mark init-method is called. + private volatile boolean isAsyncCalled = false; + + AsyncInitializeBeanMethodInvoker(Object targetObject, String beanName, String methodName) { + this.targetObject = targetObject; + this.beanName = beanName; + this.asyncMethodName = methodName; + } + + @Override + public Object invoke(final MethodInvocation invocation) throws Throwable { + // if the spring refreshing is finished + if (AsyncTaskExecutor.isStarted()) { + return invocation.getMethod().invoke(targetObject, invocation.getArguments()); + } + + Method method = invocation.getMethod(); + final String methodName = method.getName(); + if (!isAsyncCalled && methodName.equals(asyncMethodName)) { + isAsyncCalled = true; + isAsyncCalling = true; + AsyncTaskExecutor.submitTask(applicationContext.getEnvironment(), new Runnable() { + @Override + public void run() { + try { + long startTime = System.currentTimeMillis(); + invocation.getMethod().invoke(targetObject, invocation.getArguments()); + SofaLogger.info(String.format( + "%s(%s) %s method execute %dms, moduleName: %s.", targetObject + .getClass().getName(), beanName, methodName, (System + .currentTimeMillis() - startTime), moduleName)); + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + initCountDownLatch.countDown(); + isAsyncCalling = false; + } + } + }); + return null; + } + + if (isAsyncCalling) { + long startTime = System.currentTimeMillis(); + initCountDownLatch.await(); + SofaLogger.info(String.format("%s(%s) %s method wait %dms, moduleName: %s.", + targetObject.getClass().getName(), beanName, methodName, + (System.currentTimeMillis() - startTime), moduleName)); + } + return invocation.getMethod().invoke(targetObject, invocation.getArguments()); + } + } + +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ClientFactoryBeanPostProcessor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ClientFactoryBeanPostProcessor.java index 232d7e877..d5a4571b9 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ClientFactoryBeanPostProcessor.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ClientFactoryBeanPostProcessor.java @@ -88,6 +88,6 @@ public Object postProcessAfterInitialization(Object bean, String beanName) @Override public int getOrder() { - return LOWEST_PRECEDENCE; + return PriorityOrdered.HIGHEST_PRECEDENCE; } } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ExtensionClientBeanPostProcessor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ExtensionClientBeanPostProcessor.java new file mode 100644 index 000000000..9a736d1c6 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ExtensionClientBeanPostProcessor.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 com.alipay.sofa.runtime.spring; + +import com.alipay.sofa.runtime.api.aware.ExtensionClientAware; +import com.alipay.sofa.runtime.api.client.ExtensionClient; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.PriorityOrdered; + +/** + * {@link ExtensionClientAware} + * + * @author ruoshan + * @since 2.6.0 + */ +public class ExtensionClientBeanPostProcessor implements BeanPostProcessor, PriorityOrdered { + + private ExtensionClient extensionClient; + + public ExtensionClientBeanPostProcessor(ExtensionClient extensionClient) { + this.extensionClient = extensionClient; + } + + @Override + public Object postProcessBeforeInitialization(final Object bean, String beanName) + throws BeansException { + if (bean instanceof ExtensionClientAware) { + ((ExtensionClientAware) bean).setExtensionClient(extensionClient); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public int getOrder() { + return PriorityOrdered.HIGHEST_PRECEDENCE; + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java index 5d6eae020..9e4155565 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/ReferenceAnnotationBeanPostProcessor.java @@ -23,7 +23,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; -import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -51,7 +51,7 @@ * * @author xuanbei 18/5/9 */ -public class ReferenceAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered { +public class ReferenceAnnotationBeanPostProcessor implements BeanPostProcessor, PriorityOrdered { private final PlaceHolderBinder binder = new DefaultPlaceHolderBinder(); private ApplicationContext applicationContext; private SofaRuntimeContext sofaRuntimeContext; @@ -184,7 +184,7 @@ private Object createReferenceProxy(SofaReference sofaReferenceAnnotation, @Override public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; + return PriorityOrdered.HIGHEST_PRECEDENCE; } class DefaultPlaceHolderBinder implements PlaceHolderBinder { diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/SofaRuntimeContextAwareProcessor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/SofaRuntimeContextAwareProcessor.java index e049b8220..6cce8d8c7 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/SofaRuntimeContextAwareProcessor.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/SofaRuntimeContextAwareProcessor.java @@ -20,13 +20,14 @@ import com.alipay.sofa.runtime.spi.spring.SofaRuntimeContextAware; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.PriorityOrdered; /** * @author qilong.zql * @author khotyn * @since 2.5.0 */ -public class SofaRuntimeContextAwareProcessor implements BeanPostProcessor { +public class SofaRuntimeContextAwareProcessor implements BeanPostProcessor, PriorityOrdered { private SofaRuntimeContext sofaRuntimeContext; public SofaRuntimeContextAwareProcessor(SofaRuntimeContext sofaRuntimeContext) { @@ -48,4 +49,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } + + @Override + public int getOrder() { + return PriorityOrdered.HIGHEST_PRECEDENCE; + } } \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncInitBeanHolder.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncInitBeanHolder.java new file mode 100644 index 000000000..73557bed2 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncInitBeanHolder.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 com.alipay.sofa.runtime.spring.async; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author qilong.zql + * @author xuanbei + * @since 2.6.0 + */ +public class AsyncInitBeanHolder { + private static final ConcurrentMap> asyncBeanInfos = new ConcurrentHashMap>(); + + public static void registerAsyncInitBean(String moduleName, String beanId, String methodName) { + if (moduleName == null || beanId == null || methodName == null) { + return; + } + + Map asyncBeanInfosInModule = asyncBeanInfos.get(moduleName); + if (asyncBeanInfosInModule == null) { + asyncBeanInfos.putIfAbsent(moduleName, new ConcurrentHashMap()); + asyncBeanInfosInModule = asyncBeanInfos.get(moduleName); + } + + asyncBeanInfosInModule.put(beanId, methodName); + } + + public static String getAsyncInitMethodName(String moduleName, String beanId) { + Map asyncBeanInfosInModule; + asyncBeanInfosInModule = (moduleName == null) ? null : asyncBeanInfos.get(moduleName); + return (beanId == null || asyncBeanInfosInModule == null) ? null : asyncBeanInfosInModule + .get(beanId); + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncTaskExecutionListener.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncTaskExecutionListener.java new file mode 100644 index 000000000..d1ca1c36f --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncTaskExecutionListener.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 com.alipay.sofa.runtime.spring.async; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; +import org.springframework.core.PriorityOrdered; + +/** + * @author qilong.zql + * @since 2.6.0 + */ +public class AsyncTaskExecutionListener implements PriorityOrdered, + ApplicationListener, + ApplicationContextAware { + private ApplicationContext applicationContext; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + if (applicationContext.equals(event.getApplicationContext())) { + AsyncTaskExecutor.ensureAsyncTasksFinish(); + } + } + + @Override + public int getOrder() { + // invoked after {@literal com.alipay.sofa.isle.spring.listener.SofaModuleContextRefreshedListener} + return Ordered.HIGHEST_PRECEDENCE + 1; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncTaskExecutor.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncTaskExecutor.java new file mode 100644 index 000000000..e41d4d5ff --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/async/AsyncTaskExecutor.java @@ -0,0 +1,108 @@ +/* + * 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 com.alipay.sofa.runtime.spring.async; + +import com.alipay.sofa.infra.constants.SofaBootInfraConstants; +import com.alipay.sofa.runtime.spi.log.SofaLogger; +import com.alipay.sofa.runtime.util.NamedThreadFactory; +import org.springframework.core.env.Environment; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author qilong.zql + * @author xuanbei + * @since 2.6.0 + */ +public class AsyncTaskExecutor { + protected static final int CPU_COUNT = Runtime + .getRuntime() + .availableProcessors(); + protected static final AtomicReference THREAD_POOL_REF = new AtomicReference(); + + protected static final List FUTURES = new ArrayList<>(); + protected static final AtomicBoolean STARTED = new AtomicBoolean( + false); + + public static Future submitTask(Environment environment, Runnable runnable) { + if (THREAD_POOL_REF.get() == null) { + ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor(environment); + boolean success = THREAD_POOL_REF.compareAndSet(null, threadPoolExecutor); + if (!success) { + threadPoolExecutor.shutdown(); + } + } + Future future = THREAD_POOL_REF.get().submit(runnable); + FUTURES.add(future); + return future; + } + + /** + * Create thread pool to execute async init task + * @return + */ + private static ThreadPoolExecutor createThreadPoolExecutor(Environment environment) { + int threadPoolCoreSize = CPU_COUNT + 1; + String coreSizeStr = environment + .getProperty(SofaBootInfraConstants.ASYNC_INIT_BEAN_CORE_SIZE); + if (coreSizeStr != null) { + threadPoolCoreSize = Integer.parseInt(coreSizeStr); + } + + int threadPoolMaxSize = CPU_COUNT + 1; + String maxSizeStr = environment + .getProperty(SofaBootInfraConstants.ASYNC_INIT_BEAN_MAX_SIZE); + if (maxSizeStr != null) { + threadPoolMaxSize = Integer.parseInt(maxSizeStr); + } + + SofaLogger.info(String.format( + "create async-init-bean thread pool, corePoolSize: %d, maxPoolSize: %d.", + threadPoolCoreSize, threadPoolMaxSize)); + return new ThreadPoolExecutor(threadPoolCoreSize, threadPoolMaxSize, 30, TimeUnit.SECONDS, + new SynchronousQueue(), new NamedThreadFactory("async-init-bean"), + new ThreadPoolExecutor.CallerRunsPolicy()); + } + + public static void ensureAsyncTasksFinish() { + for (Future future : FUTURES) { + try { + future.get(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + STARTED.set(true); + FUTURES.clear(); + if (THREAD_POOL_REF.get() != null) { + THREAD_POOL_REF.get().shutdown(); + THREAD_POOL_REF.set(null); + } + } + + public static boolean isStarted() { + return STARTED.get(); + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/config/SofaRuntimeConfigurationProperties.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/config/SofaRuntimeConfigurationProperties.java index 32c226d62..af81275dc 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/config/SofaRuntimeConfigurationProperties.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/config/SofaRuntimeConfigurationProperties.java @@ -16,15 +16,15 @@ */ package com.alipay.sofa.runtime.spring.config; +import com.alipay.sofa.infra.constants.SofaBootInfraConstants; import com.alipay.sofa.runtime.SofaRuntimeProperties; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author xuanbei 18/5/9 */ -@ConfigurationProperties(SofaRuntimeConfigurationProperties.PREFIX) +@ConfigurationProperties(SofaBootInfraConstants.PREFIX) public class SofaRuntimeConfigurationProperties { - static final String PREFIX = "com.alipay.sofa.boot"; public void setSkipJvmReferenceHealthCheck(boolean skipJvmReferenceHealthCheck) { SofaRuntimeProperties.setSkipJvmReferenceHealthCheck(this.getClass().getClassLoader(), diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/configuration/SofaRuntimeAutoConfiguration.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/configuration/SofaRuntimeAutoConfiguration.java index b92309139..9006b4ff0 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/configuration/SofaRuntimeAutoConfiguration.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/configuration/SofaRuntimeAutoConfiguration.java @@ -16,6 +16,8 @@ */ package com.alipay.sofa.runtime.spring.configuration; +import com.alipay.sofa.runtime.spring.AsyncProxyBeanPostProcessor; +import com.alipay.sofa.runtime.spring.async.AsyncTaskExecutionListener; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -45,6 +47,16 @@ public CloseApplicationContextCallBack closeApplicationContextCallBack() { return new CloseApplicationContextCallBack(); } + @Bean + public AsyncProxyBeanPostProcessor asyncProxyBeanPostProcessor() { + return new AsyncProxyBeanPostProcessor(); + } + + @Bean + public AsyncTaskExecutionListener asyncTaskExecutionListener() { + return new AsyncTaskExecutionListener(); + } + @Configuration @ConditionalOnClass({ HealthChecker.class }) @AutoConfigureAfter(SofaRuntimeAutoConfiguration.class) diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/AbstractContractFactoryBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/AbstractContractFactoryBean.java index 10429d88a..551f4d142 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/AbstractContractFactoryBean.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/AbstractContractFactoryBean.java @@ -22,15 +22,6 @@ import javax.xml.parsers.DocumentBuilderFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.config.TypedStringValue; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; - import com.alipay.sofa.runtime.api.ServiceRuntimeException; import com.alipay.sofa.runtime.constants.SofaRuntimeFrameworkConstants; import com.alipay.sofa.runtime.service.binding.JvmBinding; @@ -40,6 +31,14 @@ import com.alipay.sofa.runtime.spi.service.BindingConverter; import com.alipay.sofa.runtime.spi.service.BindingConverterContext; import com.alipay.sofa.runtime.spi.service.BindingConverterFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; /** * Abstract Contract Factory Bean @@ -93,18 +92,26 @@ public void afterPropertiesSet() throws Exception { } sofaRuntimeContext = applicationContext.getBean( SofaRuntimeFrameworkConstants.SOFA_RUNTIME_CONTEXT_BEAN_ID, SofaRuntimeContext.class); - bindingConverterFactory = applicationContext.getBean( - SofaRuntimeFrameworkConstants.BINDING_CONVERTER_FACTORY_BEAN_ID, - BindingConverterFactory.class); - bindingAdapterFactory = applicationContext.getBean( - SofaRuntimeFrameworkConstants.BINDING_ADAPTER_FACTORY_BEAN_ID, - BindingAdapterFactory.class); + bindingConverterFactory = getBindingConverterFactory(); + bindingAdapterFactory = getBindingAdapterFactory(); if (!apiType) { this.bindings = parseBindings(tempElements, applicationContext, isInBinding()); } doAfterPropertiesSet(); } + protected BindingConverterFactory getBindingConverterFactory() { + return applicationContext.getBean( + SofaRuntimeFrameworkConstants.BINDING_CONVERTER_FACTORY_BEAN_ID, + BindingConverterFactory.class); + } + + protected BindingAdapterFactory getBindingAdapterFactory() { + return applicationContext.getBean( + SofaRuntimeFrameworkConstants.BINDING_ADAPTER_FACTORY_BEAN_ID, + BindingAdapterFactory.class); + } + protected List parseBindings(List parseElements, ApplicationContext appContext, boolean isInBinding) { List result = new ArrayList<>(); @@ -116,11 +123,7 @@ protected List parseBindings(List parseElements, .getBindingConverterByTagName(tagName); if (bindingConverter == null) { - if (!tagName.equals(SofaRuntimeFrameworkConstants.BINDING_PREFIX - + JvmBinding.JVM_BINDING_TYPE.toString())) { - throw new ServiceRuntimeException("Can't find BindingConverter of type " - + tagName); - } + dealWithbindingConverterNotExist(tagName); continue; } @@ -130,6 +133,7 @@ protected List parseBindings(List parseElements, bindingConverterContext.setAppName(sofaRuntimeContext.getAppName()); bindingConverterContext.setAppClassLoader(sofaRuntimeContext.getAppClassLoader()); bindingConverterContext.setRepeatReferLimit(repeatReferLimit); + bindingConverterContext.setBeanId(beanId); setProperties(bindingConverterContext); @@ -142,6 +146,13 @@ protected List parseBindings(List parseElements, return result; } + protected void dealWithbindingConverterNotExist(String tagName) { + if (!tagName.equals(SofaRuntimeFrameworkConstants.BINDING_PREFIX + + JvmBinding.JVM_BINDING_TYPE.toString())) { + throw new ServiceRuntimeException("Can't find BindingConverter of type " + tagName); + } + } + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ReferenceFactoryBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ReferenceFactoryBean.java index 8c6649eec..5711f6cc4 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ReferenceFactoryBean.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ReferenceFactoryBean.java @@ -30,11 +30,11 @@ * @author xuanbei 18/3/1 */ public class ReferenceFactoryBean extends AbstractContractFactoryBean { - private Object proxy; + protected Object proxy; /** jvm first or not */ - private boolean jvmFirst = true; + protected boolean jvmFirst = true; /** load balance **/ - private String loadBalance; + protected String loadBalance; public ReferenceFactoryBean() { } @@ -63,10 +63,9 @@ protected void doAfterPropertiesSet() throws Exception { @Override protected void setProperties(BindingConverterContext bindingConverterContext) { bindingConverterContext.setLoadBalance(loadBalance); - bindingConverterContext.setBeanId(beanId); } - private Reference buildReference() { + protected Reference buildReference() { return new ReferenceImpl(uniqueId, getInterfaceClass(), InterfaceMode.spring, jvmFirst); } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ServiceFactoryBean.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ServiceFactoryBean.java index 090e178c0..f04a29b0b 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ServiceFactoryBean.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/factory/ServiceFactoryBean.java @@ -33,8 +33,8 @@ * @author xuanbei 18/3/1 */ public class ServiceFactoryBean extends AbstractContractFactoryBean { - private Object ref; - private Service service; + protected Object ref; + protected Service service; public ServiceFactoryBean() { } @@ -89,7 +89,7 @@ protected void setProperties(BindingConverterContext bindingConverterContext) { bindingConverterContext.setBeanId(beanId); } - private Service buildService() { + protected Service buildService() { return new ServiceImpl(uniqueId, getInterfaceClass(), InterfaceMode.spring, ref); } diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/initializer/SofaRuntimeSpringContextInitializer.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/initializer/SofaRuntimeSpringContextInitializer.java index e54ee3efd..06cb82a9b 100644 --- a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/initializer/SofaRuntimeSpringContextInitializer.java +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/initializer/SofaRuntimeSpringContextInitializer.java @@ -26,6 +26,7 @@ import com.alipay.sofa.runtime.client.impl.ClientFactoryImpl; import com.alipay.sofa.runtime.component.impl.StandardSofaRuntimeManager; import com.alipay.sofa.runtime.constants.SofaRuntimeFrameworkConstants; +import com.alipay.sofa.runtime.ext.client.ExtensionClientImpl; import com.alipay.sofa.runtime.service.client.ReferenceClientImpl; import com.alipay.sofa.runtime.service.client.ServiceClientImpl; import com.alipay.sofa.runtime.service.impl.BindingAdapterFactoryImpl; @@ -80,19 +81,23 @@ public static void initApplicationContext(ConfigurableApplicationContext applica beanFactory.registerSingleton(SofaRuntimeFrameworkConstants.SOFA_RUNTIME_CONTEXT_BEAN_ID, sofaRuntimeContext); - beanFactory.registerSingleton( - ReferenceAnnotationBeanPostProcessor.class.getCanonicalName(), - new ReferenceAnnotationBeanPostProcessor(applicationContext, sofaRuntimeContext, - bindingAdapterFactory, bindingConverterFactory)); - beanFactory.registerSingleton(ClientFactoryBeanPostProcessor.class.getCanonicalName(), - new ClientFactoryBeanPostProcessor(sofaRuntimeContext.getClientFactory())); + // work on all bean + beanFactory.addBeanPostProcessor(new SofaRuntimeContextAwareProcessor(sofaRuntimeContext)); + beanFactory.addBeanPostProcessor(new ClientFactoryBeanPostProcessor(sofaRuntimeContext + .getClientFactory())); + beanFactory.addBeanPostProcessor(new ExtensionClientBeanPostProcessor( + new ExtensionClientImpl(sofaRuntimeContext))); beanFactory - .registerSingleton( - ApplicationShutdownCallbackPostProcessor.class.getCanonicalName(), - new ApplicationShutdownCallbackPostProcessor(sofaRuntimeContext - .getSofaRuntimeManager())); - beanFactory.registerSingleton(SofaRuntimeContextAwareProcessor.class.getCanonicalName(), - new SofaRuntimeContextAwareProcessor(sofaRuntimeContext)); + .addBeanPostProcessor(new ReferenceAnnotationBeanPostProcessor(applicationContext, + sofaRuntimeContext, bindingAdapterFactory, bindingConverterFactory)); + + // work on all bean on the beginning, then reorder after all bean post processors being registered + ApplicationShutdownCallbackPostProcessor applicationShutdownCallbackPostProcessor = new ApplicationShutdownCallbackPostProcessor( + sofaRuntimeContext.getSofaRuntimeManager()); + beanFactory.addBeanPostProcessor(applicationShutdownCallbackPostProcessor); + beanFactory.registerSingleton( + ApplicationShutdownCallbackPostProcessor.class.getCanonicalName(), + applicationShutdownCallbackPostProcessor); if (beanFactory instanceof AbstractAutowireCapableBeanFactory) { ((AbstractAutowireCapableBeanFactory) beanFactory) diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/parser/AsyncInitBeanDefinitionDecorator.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/parser/AsyncInitBeanDefinitionDecorator.java new file mode 100644 index 000000000..324cd9b98 --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/spring/parser/AsyncInitBeanDefinitionDecorator.java @@ -0,0 +1,101 @@ +/* + * 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 com.alipay.sofa.runtime.spring.parser; + +import com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport; +import com.alipay.sofa.infra.constants.SofaBootInfraConstants; +import com.alipay.sofa.runtime.spring.async.AsyncInitBeanHolder; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.xml.BeanDefinitionDecorator; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.context.support.AbstractApplicationContext; +import org.w3c.dom.Attr; +import org.w3c.dom.Node; + +/** + * @author qilong.zql + * @author xuanbei + * @since 2.6.0 + */ +public class AsyncInitBeanDefinitionDecorator implements BeanDefinitionDecorator, + SofaBootTagNameSupport { + + private static final String BEAN_LOAD_COST_FACTORY_CLASS = "com.alipay.sofa.isle.spring.factory.BeanLoadCostBeanFactory"; + private static final String GET_MODULE_NAME_METHOD = "getModuleName"; + + @Override + public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, + ParserContext parserContext) { + if (!Boolean.TRUE.toString().equalsIgnoreCase(((Attr) node).getValue())) { + return definition; + } + + String moduleName = getModuleName(parserContext); + if (moduleName != null && moduleName.trim().length() > 0) { + AsyncInitBeanHolder.registerAsyncInitBean(moduleName, definition.getBeanName(), + ((AbstractBeanDefinition) definition.getBeanDefinition()).getInitMethodName()); + } + return definition; + } + + @Override + public String supportTagName() { + return "async-init"; + } + + private String getModuleName(ParserContext parserContext) { + BeanDefinitionRegistry registry = parserContext.getRegistry(); + if (registry instanceof AbstractApplicationContext) { + BeanFactory beanFactory = ((AbstractApplicationContext) registry).getBeanFactory(); + if (isBeanLoadCostBeanFactory(beanFactory.getClass())) { + return getModuleNameFromBeanFactory(beanFactory); + } + } + + if (isBeanLoadCostBeanFactory(registry.getClass())) { + return getModuleNameFromBeanFactory(registry); + } + + return SofaBootInfraConstants.ROOT_APPLICATION_CONTEXT; + } + + public static boolean isBeanLoadCostBeanFactory(Class factoryClass) { + if (factoryClass == null) { + return false; + } + if (BEAN_LOAD_COST_FACTORY_CLASS.equals(factoryClass.getName())) { + return true; + } + + if (!Object.class.equals(factoryClass)) { + return isBeanLoadCostBeanFactory(factoryClass.getSuperclass()); + } + + return false; + } + + public static String getModuleNameFromBeanFactory(Object factory) { + try { + return (String) factory.getClass().getMethod(GET_MODULE_NAME_METHOD).invoke(factory); + } catch (Throwable e) { + return SofaBootInfraConstants.ROOT_APPLICATION_CONTEXT; + } + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/util/NamedThreadFactory.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/util/NamedThreadFactory.java new file mode 100644 index 000000000..31134b05e --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/runtime/util/NamedThreadFactory.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 com.alipay.sofa.runtime.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author qilong.zql + * @author ruoshan + * @since 2.6.0 + */ +public class NamedThreadFactory implements ThreadFactory { + + static final AtomicInteger poolNumber = new AtomicInteger(1); + final AtomicInteger threadNumber = new AtomicInteger(1); + final ThreadGroup group; + final String namePrefix; + final boolean isDaemon; + + public NamedThreadFactory(String name) { + this(name, false); + } + + public NamedThreadFactory(String prefix, boolean daemon) { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-"; + isDaemon = daemon; + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(isDaemon); + if (t.getPriority() != Thread.NORM_PRIORITY) { + t.setPriority(Thread.NORM_PRIORITY); + } + return t; + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/Extensible.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/Extensible.java new file mode 100644 index 000000000..94f25f34e --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/Extensible.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 com.alipay.sofa.service.api.component; + +/** + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ + +public interface Extensible { + /** + * register extension + * + * @param extension need to be registered + */ + void registerExtension(Extension extension) throws Exception; + + /** + * un-register extension + * + * @param extension need to be unregistered + */ + void unregisterExtension(Extension extension) throws Exception; +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/Extension.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/Extension.java new file mode 100644 index 000000000..591bd724b --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/Extension.java @@ -0,0 +1,79 @@ +/* + * 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 com.alipay.sofa.service.api.component; + +import com.alipay.sofa.runtime.api.component.ComponentName; +import org.w3c.dom.Element; + +/** + * SOFA Extension Object + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public interface Extension { + + /** + * Get component name for the extension + * + * @return Component name for the extension + */ + ComponentName getComponentName(); + + /** + * Get Target Component Name。 + * + * @return Target Component Name + */ + ComponentName getTargetComponentName(); + + /** + * Get extension point Name + * + * @return Extension point Name + */ + String getExtensionPoint(); + + /** + * Get extension element + * + * @return extension element + */ + Element getElement(); + + /** + * Get contributions + * + * @return contributions + */ + Object[] getContributions(); + + /** + * Get AppClassLoader + * @return appClassLoader + */ + ClassLoader getAppClassLoader(); + + /** + * Identifies the extension inside the contributing component. + * + * @return id + */ + String getId(); + +} diff --git a/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/ExtensionPoint.java b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/ExtensionPoint.java new file mode 100644 index 000000000..0d0ae89ff --- /dev/null +++ b/runtime-sofa-boot-starter/src/main/java/com/alipay/sofa/service/api/component/ExtensionPoint.java @@ -0,0 +1,57 @@ +/* + * 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 com.alipay.sofa.service.api.component; + +import java.util.List; + +/** + * SOFA Extension Point Object + * + * @author xi.hux@alipay.com + * @author ruoshan + * @since 2.6.0 + */ +public interface ExtensionPoint { + + /** + * Get extension point name + * + * @return extension point name + */ + String getName(); + + /** + * Get all contributions class + * + * @return all contributions class + */ + List> getContributions(); + + /** + * Get the comment + * + * @return the comment + */ + String getDocumentation(); + + /** + * Whether has contribution or not + * + * @return true or false + */ + boolean hasContribution(); +} diff --git a/runtime-sofa-boot-starter/src/main/resources/META-INF/services/com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport b/runtime-sofa-boot-starter/src/main/resources/META-INF/services/com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport index 0ef6d3383..fcb9bb2a4 100644 --- a/runtime-sofa-boot-starter/src/main/resources/META-INF/services/com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport +++ b/runtime-sofa-boot-starter/src/main/resources/META-INF/services/com.alipay.sofa.infra.config.spring.namespace.spi.SofaBootTagNameSupport @@ -36,3 +36,6 @@ com.alipay.sofa.runtime.spring.parser.ReferenceDefinitionParser com.alipay.sofa.runtime.spring.parser.ServiceDefinitionParser +com.alipay.sofa.runtime.spring.parser.AsyncInitBeanDefinitionDecorator +com.alipay.sofa.runtime.ext.spring.parser.ExtensionPointBeanDefinitionParser +com.alipay.sofa.runtime.ext.spring.parser.ExtensionBeanDefinitionParser \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/TimeWasteBean.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/TimeWasteBean.java new file mode 100644 index 000000000..a23ed189c --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/TimeWasteBean.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 com.alipay.sofa.runtime.beans; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author qilong.zql + * @since 2.6.0 + */ +public class TimeWasteBean { + private long printTime; + private static final AtomicInteger count = new AtomicInteger(0); + + public void init() throws Exception { + hello(); + TimeUnit.SECONDS.sleep(1); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + hello(); + } + }); + t.start(); + t.join(); + count.getAndIncrement(); + } + + public String hello() { + printTime = System.currentTimeMillis(); + return "world"; + } + + public long getPrintTime() { + return printTime; + } + + public static int getCount() { + return count.get(); + } + + public static void resetCount() { + count.set(0); + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/impl/ComponentLifeCycleServiceImpl.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/impl/ComponentLifeCycleServiceImpl.java new file mode 100644 index 000000000..5e3220b64 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/impl/ComponentLifeCycleServiceImpl.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 com.alipay.sofa.runtime.beans.impl; + +import com.alipay.sofa.runtime.api.ServiceRuntimeException; +import com.alipay.sofa.runtime.api.component.ComponentLifeCycle; +import com.alipay.sofa.runtime.beans.service.LifeCycleService; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class ComponentLifeCycleServiceImpl implements LifeCycleService, ComponentLifeCycle { + + private boolean activated; + private boolean deactivated; + + @Override + public void activate() throws ServiceRuntimeException { + activated = true; + } + + @Override + public void deactivate() throws ServiceRuntimeException { + deactivated = true; + } + + @Override + public boolean isActivated() { + return activated; + } + + @Override + public boolean isDeactivated() { + return deactivated; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/service/LifeCycleService.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/service/LifeCycleService.java new file mode 100644 index 000000000..a4a001219 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/beans/service/LifeCycleService.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 com.alipay.sofa.runtime.beans.service; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public interface LifeCycleService { + + boolean isActivated(); + + boolean isDeactivated(); +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/AsyncInitTest.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/AsyncInitTest.java new file mode 100644 index 000000000..141950864 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/AsyncInitTest.java @@ -0,0 +1,75 @@ +/* + * 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 com.alipay.sofa.runtime.integration; + +import com.alipay.sofa.runtime.beans.TimeWasteBean; +import com.alipay.sofa.runtime.integration.base.TestBase; +import com.alipay.sofa.runtime.spring.async.AsyncTaskExecutor; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author qilong.zql + * @since 2.6.0 + */ +public class AsyncInitTest extends TestBase { + + @Before + public void before() { + try { + Field field = AsyncTaskExecutor.class.getDeclaredField("STARTED"); + field.setAccessible(true); + AtomicBoolean atomicBoolean = (AtomicBoolean) field.get(null); + atomicBoolean.set(false); + } catch (Throwable throwable) { + // ignore; + } + } + + @Test + public void testAsyncInitBean() { + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + initApplicationContext(new HashMap(), EmptyConfiguration.class); + Assert.assertEquals(12, TimeWasteBean.getCount()); + for (int i = 1; i <= 10; i++) { + TimeWasteBean bean = applicationContext.getBean("testBean" + i, TimeWasteBean.class); + if (bean.getPrintTime() < min) { + min = bean.getPrintTime(); + } + if (bean.getPrintTime() > max) { + max = bean.getPrintTime(); + } + } + Assert.assertTrue("max:" + max + ", min:" + min, max - min < 3500); + TimeWasteBean.resetCount(); + } + + @EnableAutoConfiguration + @Configuration + @ImportResource({ "classpath*:META-INF/async/*.xml" }) + static class EmptyConfiguration { + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/IntegrationTest.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/IntegrationTest.java index c2a96d424..9b3ed51fb 100644 --- a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/IntegrationTest.java +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/IntegrationTest.java @@ -129,7 +129,7 @@ public void testFactoryBean() { .getBean("&" + SofaBeanNameGenerator.generateSofaReferenceBeanName(SampleService.class, "methodBeanClassAnnotationSampleService")); - Assert.assertTrue(serviceFactoryBean.isApiType()); + Assert.assertTrue(referenceFactoryBean.isApiType()); /** * {@link com.alipay.sofa.runtime.integration.base.AbstractTestBase.IntegrationTestConfiguration.BeforeConfiguration} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/TestSofaServiceAndReferenceException.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/TestSofaServiceAndReferenceException.java index 9bcb27bb7..4fc6ceb79 100644 --- a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/TestSofaServiceAndReferenceException.java +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/TestSofaServiceAndReferenceException.java @@ -23,7 +23,11 @@ import com.alipay.sofa.runtime.api.annotation.SofaReference; import com.alipay.sofa.runtime.api.annotation.SofaReferenceBinding; +import com.alipay.sofa.runtime.api.annotation.SofaServiceBinding; +import com.alipay.sofa.runtime.api.binding.BindingType; +import com.alipay.sofa.runtime.spring.factory.ServiceFactoryBean; import org.apache.commons.io.FileUtils; +import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.FatalBeanException; @@ -43,6 +47,13 @@ */ public class TestSofaServiceAndReferenceException extends TestBase { + @After + public void closeApplication() { + if (applicationContext != null && applicationContext.isActive()) { + applicationContext.close(); + } + } + @Test public void testSofaReferenceOnMethodParameter() { Map properties = new HashMap<>(); @@ -60,8 +71,7 @@ public void testSofaReferenceOnMethodParameter() { @Test public void testMultiSofaServiceWithSameInterfaceAndUniqueId() throws IOException { - File sofaLog = new File(System.getProperty("user.home") + File.separator + "logs" - + File.separator + "sofa-runtime" + File.separator + File sofaLog = new File("./logs" + File.separator + "sofa-runtime" + File.separator + "common-error.log"); FileUtils.write(sofaLog, "", System.getProperty("file.encoding")); Map properties = new HashMap<>(); @@ -89,6 +99,29 @@ public void testMultiSofaReferenceFactoryMethod() { initApplicationContext(properties, EmptyConfiguration.class); } + @Test + public void testSofaServiceWithMultipleBindings() { + Map properties = new HashMap<>(); + properties.put("spring.application.name", "runtime-test"); + initApplicationContext(properties, MultipleBindingsSofaServiceConfiguration.class); + ServiceFactoryBean bean = applicationContext.getBean(ServiceFactoryBean.class); + Assert.assertEquals(2, bean.getBindings().size()); + Assert.assertEquals(new BindingType("jvm"), bean.getBindings().get(0).getBindingType()); + Assert.assertEquals(new BindingType("jvm"), bean.getBindings().get(1).getBindingType()); + } + + @Configuration + @EnableAutoConfiguration + static class MultipleBindingsSofaServiceConfiguration { + + // since the sofa-boot does not have any binding converter implementation, we can use two jvm bindings for now. + @Bean + @SofaService(bindings = { @SofaServiceBinding, @SofaServiceBinding }) + SampleService sampleService() { + return new SampleServiceImpl("test"); + } + } + @EnableAutoConfiguration @Configuration static class EmptyConfiguration { diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/AbstractTestBase.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/AbstractTestBase.java index 8a494e24a..ceffbf76f 100644 --- a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/AbstractTestBase.java +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/AbstractTestBase.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/SofaBootTestApplication.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/bootstrap/SofaBootTestApplication.java similarity index 95% rename from runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/SofaBootTestApplication.java rename to runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/bootstrap/SofaBootTestApplication.java index 0bdbb501e..225c03836 100644 --- a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/base/SofaBootTestApplication.java +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/bootstrap/SofaBootTestApplication.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alipay.sofa.runtime.integration.base; +package com.alipay.sofa.runtime.integration.bootstrap; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.ImportResource; diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/component/ComponentLifeCycleTest.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/component/ComponentLifeCycleTest.java new file mode 100644 index 000000000..49b768b25 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/component/ComponentLifeCycleTest.java @@ -0,0 +1,57 @@ +/* + * 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 com.alipay.sofa.runtime.integration.component; + +import com.alipay.sofa.runtime.beans.service.LifeCycleService; +import com.alipay.sofa.runtime.integration.bootstrap.SofaBootTestApplication; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SofaBootTestApplication.class) +public class ComponentLifeCycleTest { + + @Autowired + private LifeCycleService lifeCycleService; + + @Autowired + private SofaRuntimeContext sofaRuntimeContext; + + @Test + public void testActivated() { + Assert.assertTrue(lifeCycleService.isActivated()); + } + + @Test + @DirtiesContext + public void testDeactivated() { + Assert.assertFalse(lifeCycleService.isDeactivated()); + sofaRuntimeContext.getComponentManager().shutdown(); + Assert.assertTrue(lifeCycleService.isDeactivated()); + } + +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/ExtensionTest.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/ExtensionTest.java new file mode 100644 index 000000000..9a4762951 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/ExtensionTest.java @@ -0,0 +1,231 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension; + +import com.alipay.sofa.common.xmap.Context; +import com.alipay.sofa.common.xmap.DOMSerializer; +import com.alipay.sofa.common.xmap.Resource; +import com.alipay.sofa.common.xmap.XMap; +import com.alipay.sofa.runtime.api.aware.ExtensionClientAware; +import com.alipay.sofa.runtime.api.client.ExtensionClient; +import com.alipay.sofa.runtime.api.client.param.ExtensionParam; +import com.alipay.sofa.runtime.api.client.param.ExtensionPointParam; +import com.alipay.sofa.runtime.integration.bootstrap.SofaBootTestApplication; +import com.alipay.sofa.runtime.integration.extension.bean.IExtension; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringBean; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringListBean; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringMapBean; +import com.alipay.sofa.runtime.integration.extension.descriptor.ClientExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.SimpleExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.XMapTestDescriptor; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.OutputStream; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = SofaBootTestApplication.class) +public class ExtensionTest implements ExtensionClientAware { + + private ExtensionClient extensionClient; + + @Autowired + private IExtension iExtension; + + @Autowired + private SimpleSpringBean simpleSpringBean; + + @Autowired + private SimpleSpringListBean simpleSpringListBean1; + + @Autowired + private SimpleSpringListBean simpleSpringListBean2; + + @Autowired + private SimpleSpringMapBean springMapBean1; + + @Autowired + private SimpleSpringMapBean springMapBean2; + + @Test + public void testXMap() throws Exception { + XMap xMap = new XMap(); + Resource resource = new Resource(new Context(), "META-INF/extension/extension-xmap.xml"); + xMap.register(XMapTestDescriptor.class, true); + Object object = xMap.load(resource.toURL()); + Assert.assertNotNull(object); + Assert.assertTrue(object instanceof XMapTestDescriptor); + XMapTestDescriptor xMapTestDescriptor = (XMapTestDescriptor) object; + Assert.assertEquals("xmaptest", xMapTestDescriptor.getValue()); + Assert.assertEquals(1, xMap.loadAll(resource.toURL()).length); + } + + @Test + public void testXMapAsString() throws Exception { + XMap xMap = new XMap(); + xMap.register(XMapTestDescriptor.class); + Document document = xMap.asXml(new XMapTestDescriptor(), null); + Assert.assertNotNull(document); + Assert.assertEquals("xmaptest", document.getDocumentElement().getTagName()); + + String value = DOMSerializer.toString(document); + Assert.assertNotNull(value); + Assert.assertTrue(value.contains("xmaptest")); + + OutputStream out = new ByteArrayOutputStream(); + DOMSerializer.write(document, out); + Assert.assertNotNull(out); + Assert.assertTrue(out.toString().contains("xmaptest")); + + DOMSerializer.write(document.getDocumentElement(), out); + Assert.assertNotNull(out); + Assert.assertTrue(out.toString().contains("xmaptest")); + } + + @Test + public void testExtensionClient() throws Exception { + ExtensionPointParam extensionPointParam = new ExtensionPointParam(); + extensionPointParam.setName("clientValue"); + extensionPointParam.setTargetName("iExtension"); + extensionPointParam.setTarget(iExtension); + extensionPointParam.setContributionClass(ClientExtensionDescriptor.class); + extensionClient.publishExtensionPoint(extensionPointParam); + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(new File(Thread.currentThread().getContextClassLoader() + .getResource("META-INF/extension/extension.xml").toURI())); + ExtensionParam extensionParam = new ExtensionParam(); + extensionParam.setTargetName("clientValue"); + extensionParam.setTargetInstanceName("iExtension"); + extensionParam.setElement(doc.getDocumentElement()); + extensionClient.publishExtension(extensionParam); + + Assert.assertEquals("SOFABoot Extension Client Test", iExtension.getClientValue()); + } + + @Test + public void testSimple() throws Exception { + Assert.assertNotNull(iExtension); + Assert.assertNotNull(iExtension.getSimpleExtensionDescriptor()); + Assert.assertEquals("SOFABoot Extension Test", iExtension.getSimpleExtensionDescriptor() + .getStringValue()); + Assert.assertEquals("value with path", iExtension.getSimpleExtensionDescriptor() + .getStringValueWithPath()); + Assert.assertEquals(new Integer(10), iExtension.getSimpleExtensionDescriptor() + .getIntValue()); + Assert.assertEquals(new Long(20), iExtension.getSimpleExtensionDescriptor().getLongValue()); + Assert.assertEquals(new Float(1.1), iExtension.getSimpleExtensionDescriptor() + .getFloatValue()); + Assert.assertEquals(new Double(2.2), iExtension.getSimpleExtensionDescriptor() + .getDoubleValue()); + Assert.assertEquals(Boolean.TRUE, iExtension.getSimpleExtensionDescriptor() + .getBooleanValue()); + Assert.assertTrue(iExtension.getSimpleExtensionDescriptor().getDateValue().toString() + .contains("2019")); + Assert.assertEquals("file", iExtension.getSimpleExtensionDescriptor().getFileValue() + .getName()); + Assert.assertEquals(SimpleExtensionDescriptor.class.getName(), iExtension + .getSimpleExtensionDescriptor().getClassValue().getName()); + Assert.assertEquals("http://test", iExtension.getSimpleExtensionDescriptor().getUrlValue() + .toString()); + Assert.assertEquals("extension.xml", iExtension.getSimpleExtensionDescriptor() + .getResourceValue().toFile().getName()); + } + + @Test + public void testList() { + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getValues().size()); + Assert.assertTrue(iExtension.getListExtensionDescriptor().getValues().contains("test1")); + Assert.assertTrue(iExtension.getListExtensionDescriptor().getValues().contains("test2")); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getIntValues().length); + Assert.assertEquals(1, iExtension.getListExtensionDescriptor().getIntValues()[0]); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getIntValues()[1]); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getLongValues().length); + Assert.assertEquals(11, iExtension.getListExtensionDescriptor().getLongValues()[0]); + Assert.assertEquals(22, iExtension.getListExtensionDescriptor().getLongValues()[1]); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getFloatValues().length); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getDoubleValues().length); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getBooleanValues().length); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getCharValues().length); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getShortValues().length); + Assert.assertEquals(2, iExtension.getListExtensionDescriptor().getByteValues().length); + } + + @Test + public void testMap() { + Assert.assertEquals(2, iExtension.getTestMap().size()); + Assert.assertEquals("testMapValue1", iExtension.getTestMap().get("testMapKey1")); + Assert.assertEquals("testMapValue2", iExtension.getTestMap().get("testMapKey2")); + } + + @Test + public void testSimpleString() { + Assert.assertNotNull(iExtension.getSimpleSpringBean()); + Assert.assertEquals(iExtension.getSimpleSpringBean(), simpleSpringBean); + } + + @Test + public void testSpringList() { + Assert.assertEquals(2, iExtension.getSimpleSpringListBeans().size()); + Assert.assertTrue(iExtension.getSimpleSpringListBeans().contains(simpleSpringListBean1)); + Assert.assertTrue(iExtension.getSimpleSpringListBeans().contains(simpleSpringListBean2)); + } + + @Test + public void testSpringMap() { + Assert.assertEquals(2, iExtension.getSimpleSpringMapBeanMap().size()); + Assert.assertEquals(springMapBean1, + iExtension.getSimpleSpringMapBeanMap().get("testMapSpringKey1")); + Assert.assertEquals(springMapBean2, + iExtension.getSimpleSpringMapBeanMap().get("testMapSpringKey2")); + } + + @Test + public void testContext() { + Assert.assertEquals("testContextValue\n", iExtension.getTestContextValue()); + } + + @Test + public void testParent() { + Assert.assertEquals("testParentValue", iExtension.getTestParentValue()); + } + + @Test + public void testBad() { + Assert.assertNull(iExtension.getBadDescriptor()); + } + + @Override + public void setExtensionClient(ExtensionClient extensionClient) { + this.extensionClient = extensionClient; + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/IExtension.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/IExtension.java new file mode 100755 index 000000000..325246c2a --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/IExtension.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 com.alipay.sofa.runtime.integration.extension.bean; + +import com.alipay.sofa.runtime.integration.extension.descriptor.ListExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.SimpleExtensionDescriptor; + +import java.util.List; +import java.util.Map; + +/** + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public interface IExtension { + + /** + * Extension Client Test + */ + String getClientValue(); + + /** + * Simple XNode Test + */ + SimpleExtensionDescriptor getSimpleExtensionDescriptor(); + + /** + * XNodeList Test + */ + ListExtensionDescriptor getListExtensionDescriptor(); + + /** + * XNodeMap Test + */ + Map getTestMap(); + + /** + * XNodeSpring Test + */ + SimpleSpringBean getSimpleSpringBean(); + + /** + * XNodeSpringList Test + */ + List getSimpleSpringListBeans(); + + /** + * XNodeSpringMap Test + */ + Map getSimpleSpringMapBeanMap(); + + /** + * XContext Test + */ + String getTestContextValue(); + + /** + * XParent Test + */ + String getTestParentValue(); + + /** + * Bad Test + */ + SimpleExtensionDescriptor getBadDescriptor(); + +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/IExtensionImpl.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/IExtensionImpl.java new file mode 100755 index 000000000..73b90568b --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/IExtensionImpl.java @@ -0,0 +1,149 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.bean; + +import com.alipay.sofa.service.api.component.Extension; +import com.alipay.sofa.runtime.integration.extension.descriptor.ClientExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.ContextExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.MapExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.ParentExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.SimpleExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.ListExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.SpringListExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.SpringMapExtensionDescriptor; +import com.alipay.sofa.runtime.integration.extension.descriptor.SpringSimpleExtensionDescriptor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +public class IExtensionImpl implements IExtension { + + private String clientValue; + + private SimpleExtensionDescriptor simpleExtensionDescriptor; + + private ListExtensionDescriptor listExtensionDescriptor; + + private Map testMap = new HashMap<>(); + + private SimpleSpringBean simpleSpringBean; + + private List simpleSpringListBeans = new ArrayList<>(); + + private Map simpleSpringMapBeanMap = new HashMap<>(); + + private String testContextValue; + + private String testParentValue; + + private SimpleExtensionDescriptor badDescriptor; + + @Override + public String getClientValue() { + return clientValue; + } + + public SimpleExtensionDescriptor getSimpleExtensionDescriptor() { + return simpleExtensionDescriptor; + } + + public ListExtensionDescriptor getListExtensionDescriptor() { + return listExtensionDescriptor; + } + + @Override + public Map getTestMap() { + return testMap; + } + + @Override + public SimpleSpringBean getSimpleSpringBean() { + return simpleSpringBean; + } + + @Override + public List getSimpleSpringListBeans() { + return simpleSpringListBeans; + } + + @Override + public Map getSimpleSpringMapBeanMap() { + return simpleSpringMapBeanMap; + } + + public String getTestContextValue() { + return testContextValue; + } + + public String getTestParentValue() { + return testParentValue; + } + + public SimpleExtensionDescriptor getBadDescriptor() { + return badDescriptor; + } + + /** + * Component method, framework will invoke this method to contribute the extension to the existing extension point. + * + * @param extension extension + * @throws Exception any exception + */ + public void registerExtension(Extension extension) throws Exception { + Object[] contributions = extension.getContributions(); + String extensionPoint = extension.getExtensionPoint(); + + if (contributions == null) { + return; + } + + for (Object contribution : contributions) { + if ("clientValue".equals(extensionPoint)) { + clientValue = ((ClientExtensionDescriptor) contribution).getValue(); + } else if ("simple".equals(extensionPoint)) { + simpleExtensionDescriptor = (SimpleExtensionDescriptor) contribution; + } else if ("testList".equals(extensionPoint)) { + listExtensionDescriptor = (ListExtensionDescriptor) contribution; + } else if ("testMap".equals(extensionPoint)) { + testMap.putAll(((MapExtensionDescriptor) contribution).getValues()); + } else if ("simpleSpring".equals(extensionPoint)) { + simpleSpringBean = ((SpringSimpleExtensionDescriptor) contribution).getValue(); + } else if ("testSpringList".equals(extensionPoint)) { + simpleSpringListBeans.addAll(((SpringListExtensionDescriptor) contribution) + .getValues()); + } else if ("testSpringMap".equals(extensionPoint)) { + simpleSpringMapBeanMap.putAll(((SpringMapExtensionDescriptor) contribution) + .getValues()); + } else if ("testContext".equals(extensionPoint)) { + testContextValue = ((ContextExtensionDescriptor) contribution).getContextValue(); + } else if ("testParent".equals(extensionPoint)) { + testParentValue = ((ParentExtensionDescriptor) contribution) + .getSubExtensionDescriptor().getParentValue().getValue(); + } else if ("bad".equals(extensionPoint)) { + badDescriptor = (SimpleExtensionDescriptor) contribution; + ; + } + } + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringBean.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringBean.java new file mode 100644 index 000000000..1e43cd434 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringBean.java @@ -0,0 +1,25 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.bean; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class SimpleSpringBean { +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringListBean.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringListBean.java new file mode 100644 index 000000000..9bee02b83 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringListBean.java @@ -0,0 +1,25 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.bean; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class SimpleSpringListBean { +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringMapBean.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringMapBean.java new file mode 100644 index 000000000..6accbd80b --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringMapBean.java @@ -0,0 +1,25 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.bean; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class SimpleSpringMapBean { +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringMapBeanWithXObject.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringMapBeanWithXObject.java new file mode 100644 index 000000000..e3fcb85d0 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/bean/SimpleSpringMapBeanWithXObject.java @@ -0,0 +1,28 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.bean; + +import com.alipay.sofa.common.xmap.annotation.XObject; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +@XObject("springMap") +public class SimpleSpringMapBeanWithXObject { +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ClientExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ClientExtensionDescriptor.java new file mode 100755 index 000000000..338dfafb1 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ClientExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XNode; +import com.alipay.sofa.common.xmap.annotation.XObject; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("clientValue") +public class ClientExtensionDescriptor { + @XNode("value") + private String value; + + public String getValue() { + return value; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ContextExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ContextExtensionDescriptor.java new file mode 100755 index 000000000..b05e507ef --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ContextExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XContent; +import com.alipay.sofa.common.xmap.annotation.XObject; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("testContext") +public class ContextExtensionDescriptor { + + @XContent(value = "value") + private String contextValue; + + public String getContextValue() { + return contextValue; + } + + public void setContextValue(String contextValue) { + this.contextValue = contextValue; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ListExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ListExtensionDescriptor.java new file mode 100755 index 000000000..7ee818f26 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ListExtensionDescriptor.java @@ -0,0 +1,105 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XNodeList; +import com.alipay.sofa.common.xmap.annotation.XObject; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("testList") +public class ListExtensionDescriptor { + + @XNodeList(value = "value", componentType = String.class, type = ArrayList.class) + private List values; + + @XNodeList(value = "attribute/value[@id='listTest']", componentType = String.class, type = String[].class) + private String[] attributeValues; + + @XNodeList(value = "value", componentType = String.class, type = LinkedList.class) + private List LinkedListValues; + + @XNodeList(value = "intValue", componentType = int.class, type = int[].class) + int[] intValues; + + @XNodeList(value = "longValue", componentType = long.class, type = long[].class) + long[] longValues; + + @XNodeList(value = "floatValue", componentType = float.class, type = float[].class) + float[] floatValues; + + @XNodeList(value = "doubleValue", componentType = double.class, type = double[].class) + double[] doubleValues; + + @XNodeList(value = "booleanValue", componentType = boolean.class, type = boolean[].class) + boolean[] booleanValues; + + @XNodeList(value = "charValue", componentType = char.class, type = char[].class) + char[] charValues; + + @XNodeList(value = "shortValue", componentType = short.class, type = short[].class) + short[] shortValues; + + @XNodeList(value = "byteValue", componentType = byte.class, type = byte[].class) + byte[] byteValues; + + public List getValues() { + return values; + } + + public String[] getAttributeValues() { + return attributeValues; + } + + public int[] getIntValues() { + return intValues; + } + + public long[] getLongValues() { + return longValues; + } + + public float[] getFloatValues() { + return floatValues; + } + + public double[] getDoubleValues() { + return doubleValues; + } + + public boolean[] getBooleanValues() { + return booleanValues; + } + + public char[] getCharValues() { + return charValues; + } + + public short[] getShortValues() { + return shortValues; + } + + public byte[] getByteValues() { + return byteValues; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/MapExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/MapExtensionDescriptor.java new file mode 100755 index 000000000..eff2d2a13 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/MapExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XNodeMap; +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringMapBean; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("testMap") +public class MapExtensionDescriptor { + + @XNodeMap(value = "value", key = "@name", componentType = String.class, type = HashMap.class) + private Map values; + + @XNodeMap(value = "simple", key = "@key", componentType = SimpleExtensionDescriptor.class, type = HashMap.class) + private Map simpleValues; + + @XNodeMap(value = "value/attribute[@id='mapTest']", key = "@name", componentType = SimpleSpringMapBean.class, type = HashMap.class) + private Map valueAttributes; + + public Map getValues() { + return values; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ParentExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ParentExtensionDescriptor.java new file mode 100755 index 000000000..fe7ca12fc --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/ParentExtensionDescriptor.java @@ -0,0 +1,50 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XNode; +import com.alipay.sofa.common.xmap.annotation.XObject; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject(value = "testParent", order = { "a", "b" }) +public class ParentExtensionDescriptor { + + @XNode("value") + private String value; + + @XNode("testSub") + private SubExtensionDescriptor subExtensionDescriptor; + + public SubExtensionDescriptor getSubExtensionDescriptor() { + return subExtensionDescriptor; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public void setSubExtensionDescriptor(SubExtensionDescriptor subExtensionDescriptor) { + this.subExtensionDescriptor = subExtensionDescriptor; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SimpleExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SimpleExtensionDescriptor.java new file mode 100755 index 000000000..02a7c9992 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SimpleExtensionDescriptor.java @@ -0,0 +1,166 @@ +/* + * 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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.Resource; +import com.alipay.sofa.common.xmap.annotation.XNode; +import com.alipay.sofa.common.xmap.annotation.XObject; + +import java.io.File; +import java.net.URL; +import java.util.Date; + +/** + * @author khotyn + * @author ruoshan + * @since 2.6.0 + */ +@XObject("simple") +public class SimpleExtensionDescriptor { + + @XNode("value") + private String stringValue; + + @XNode("path/value") + private String stringValueWithPath; + + @XNode("intValue") + private Integer intValue; + + @XNode("longValue") + private Long longValue; + + @XNode("floatValue") + private Float floatValue; + + @XNode("doubleValue") + private Double doubleValue; + + @XNode("booleanValue") + private Boolean booleanValue; + + @XNode("dateValue") + private Date dateValue; + + @XNode("fileValue") + private File fileValue; + + @XNode("urlValue") + private URL urlValue; + + @XNode("classValue") + private Class classValue; + + @XNode("resourceValue") + private Resource resourceValue; + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + public String getStringValueWithPath() { + return stringValueWithPath; + } + + public void setStringValueWithPath(String stringValueWithPath) { + this.stringValueWithPath = stringValueWithPath; + } + + public Integer getIntValue() { + return intValue; + } + + public void setIntValue(Integer intValue) { + this.intValue = intValue; + } + + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + + public Float getFloatValue() { + return floatValue; + } + + public void setFloatValue(Float floatValue) { + this.floatValue = floatValue; + } + + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + + public Date getDateValue() { + return dateValue; + } + + public void setDateValue(Date dateValue) { + this.dateValue = dateValue; + } + + public File getFileValue() { + return fileValue; + } + + public void setFileValue(File fileValue) { + this.fileValue = fileValue; + } + + public URL getUrlValue() { + return urlValue; + } + + public void setUrlValue(URL urlValue) { + this.urlValue = urlValue; + } + + public Class getClassValue() { + return classValue; + } + + public void setClassValue(Class classValue) { + this.classValue = classValue; + } + + public Resource getResourceValue() { + return resourceValue; + } + + public void setResourceValue(Resource resourceValue) { + this.resourceValue = resourceValue; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringListExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringListExtensionDescriptor.java new file mode 100755 index 000000000..5da5e3390 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringListExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.common.xmap.spring.XNodeListSpring; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringListBean; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("testSpringList") +public class SpringListExtensionDescriptor { + + @XNodeListSpring(value = "value", componentType = SimpleSpringListBean.class, type = ArrayList.class) + private List values; + + public List getValues() { + return values; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringMapExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringMapExtensionDescriptor.java new file mode 100755 index 000000000..823c3458e --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringMapExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.common.xmap.spring.XNodeMapSpring; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringMapBean; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringMapBeanWithXObject; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("testSpringMap") +public class SpringMapExtensionDescriptor { + + @XNodeMapSpring(value = "value", key = "@name", componentType = SimpleSpringMapBean.class, type = HashMap.class) + private Map values; + + @XNodeMapSpring(value = "value/attribute[@id='springMapTest']", key = "@name", componentType = SimpleSpringMapBeanWithXObject.class, type = HashMap.class) + private Map valueAttributes; + + public Map getValues() { + return values; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringSimpleExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringSimpleExtensionDescriptor.java new file mode 100755 index 000000000..35ef6de1e --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SpringSimpleExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.common.xmap.spring.XNodeSpring; +import com.alipay.sofa.runtime.integration.extension.bean.SimpleSpringBean; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject("simpleSpring") +public class SpringSimpleExtensionDescriptor { + + @XNodeSpring(value = "value", type = SimpleSpringBean.class) + private SimpleSpringBean value; + + /** + * + * @return + */ + public SimpleSpringBean getValue() { + return value; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SubExtensionDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SubExtensionDescriptor.java new file mode 100644 index 000000000..4ca83e8c0 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/SubExtensionDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XObject; +import com.alipay.sofa.common.xmap.annotation.XParent; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +@XObject("testSub") +public class SubExtensionDescriptor { + + @XParent + private ParentExtensionDescriptor parentValue; + + public ParentExtensionDescriptor getParentValue() { + return parentValue; + } + + @XParent + public void setParentValue(ParentExtensionDescriptor parentValue) { + this.parentValue = parentValue; + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/XMapTestDescriptor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/XMapTestDescriptor.java new file mode 100755 index 000000000..8a29b7e57 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/extension/descriptor/XMapTestDescriptor.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 com.alipay.sofa.runtime.integration.extension.descriptor; + +import com.alipay.sofa.common.xmap.annotation.XNode; +import com.alipay.sofa.common.xmap.annotation.XNodeList; +import com.alipay.sofa.common.xmap.annotation.XObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author ruoshan + * @since 2.6.0 + */ +@XObject(value = "xmaptest", order = { "value1", "value2" }) +public class XMapTestDescriptor { + @XNode("value") + private String value; + + @XNodeList(value = "values", trim = false, type = ArrayList.class, componentType = String.class) + private List values = new ArrayList<>(); + + public String getValue() { + return value; + } + + public List getValues() { + return values; + } +} diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/BeanPostProcessorOrderTest.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/BeanPostProcessorOrderTest.java new file mode 100644 index 000000000..ef8224e37 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/BeanPostProcessorOrderTest.java @@ -0,0 +1,98 @@ +/* + * 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 com.alipay.sofa.runtime.integration.postprocessor; + +import com.alipay.sofa.runtime.api.event.ApplicationShutdownCallback; +import com.alipay.sofa.runtime.integration.bootstrap.SofaBootTestApplication; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeManager; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SofaBootTestApplication.class) +public class BeanPostProcessorOrderTest { + + @Autowired + private SofaRuntimeContext sofaRuntimeContext; + + @Autowired + private HigherOrderBeanPostProcessor higherOrderBeanPostProcessor; + + @Autowired + private BeanPostProcessorTestBean beanPostProcessorTestBean; + + @Test + public void testClientFactoryInject() { + Assert.assertNotNull(higherOrderBeanPostProcessor.getClientFactory()); + } + + @Test + public void testSofaRuntimeContextInject() { + Assert.assertNotNull(higherOrderBeanPostProcessor.getSofaRuntimeContext()); + } + + @Test + public void testShutDownHook() throws Exception { + List applicationShutdownCallbacks = getApplicationShutdownCallbacks(); + Assert.assertNotNull(applicationShutdownCallbacks); + Assert.assertTrue(applicationShutdownCallbacks.contains(higherOrderBeanPostProcessor)); + } + + @Test + @SuppressWarnings("unchecked") + public void testShutDownHookReordered() throws Exception { + List applicationShutdownCallbacks = getApplicationShutdownCallbacks(); + BeanPostProcessorTestBean testBeanCallBack = null; + for (ApplicationShutdownCallback applicationShutdownCallback : applicationShutdownCallbacks) { + if (BeanPostProcessorTestBean.class.getName().equals( + applicationShutdownCallback.getClass().getName())) { + testBeanCallBack = (BeanPostProcessorTestBean) applicationShutdownCallback; + } + } + Assert.assertNotNull(testBeanCallBack); + Assert.assertEquals(testBeanCallBack, beanPostProcessorTestBean); + Assert.assertTrue(testBeanCallBack.isEnhancedByLowOrderPostProcessor()); + } + + @Test + public void testSofaReference() throws Exception { + Assert.assertNotNull(higherOrderBeanPostProcessor.getSampleService()); + } + + @SuppressWarnings("unchecked") + private List getApplicationShutdownCallbacks() throws Exception { + SofaRuntimeManager sofaRuntimeManager = sofaRuntimeContext.getSofaRuntimeManager(); + Field applicationShutdownCallbacksField = sofaRuntimeManager.getClass().getDeclaredField( + "applicationShutdownCallbacks"); + applicationShutdownCallbacksField.setAccessible(true); + return (List) applicationShutdownCallbacksField + .get(sofaRuntimeManager); + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/BeanPostProcessorTestBean.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/BeanPostProcessorTestBean.java new file mode 100644 index 000000000..da443533b --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/BeanPostProcessorTestBean.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 com.alipay.sofa.runtime.integration.postprocessor; + +import com.alipay.sofa.runtime.api.event.ApplicationShutdownCallback; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class BeanPostProcessorTestBean implements ApplicationShutdownCallback { + + private boolean enhancedByLowOrderPostProcessor = false; + + public BeanPostProcessorTestBean() { + } + + public BeanPostProcessorTestBean(boolean enhancedByLowOrderPostProcessor) { + this.enhancedByLowOrderPostProcessor = enhancedByLowOrderPostProcessor; + } + + public boolean isEnhancedByLowOrderPostProcessor() { + return enhancedByLowOrderPostProcessor; + } + + @Override + public void shutdown() throws Throwable { + + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/HigherOrderBeanPostProcessor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/HigherOrderBeanPostProcessor.java new file mode 100644 index 000000000..3dd4a3f1c --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/HigherOrderBeanPostProcessor.java @@ -0,0 +1,89 @@ +/* + * 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 com.alipay.sofa.runtime.integration.postprocessor; + +import com.alipay.sofa.runtime.api.annotation.SofaReference; +import com.alipay.sofa.runtime.api.aware.ClientFactoryAware; +import com.alipay.sofa.runtime.api.client.ClientFactory; +import com.alipay.sofa.runtime.api.event.ApplicationShutdownCallback; +import com.alipay.sofa.runtime.beans.service.SampleService; +import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; +import com.alipay.sofa.runtime.spi.spring.SofaRuntimeContextAware; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.PriorityOrdered; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class HigherOrderBeanPostProcessor implements BeanPostProcessor, PriorityOrdered, + SofaRuntimeContextAware, ClientFactoryAware, + ApplicationShutdownCallback { + + private SofaRuntimeContext sofaRuntimeContext; + + private ClientFactory clientFactory; + + @SofaReference + private SampleService sampleService; + + @Override + public int getOrder() { + return PriorityOrdered.HIGHEST_PRECEDENCE; + } + + @Override + public void setClientFactory(ClientFactory clientFactory) { + this.clientFactory = clientFactory; + } + + @Override + public void setSofaRuntimeContext(SofaRuntimeContext sofaRuntimeContext) { + this.sofaRuntimeContext = sofaRuntimeContext; + } + + public SofaRuntimeContext getSofaRuntimeContext() { + return sofaRuntimeContext; + } + + public ClientFactory getClientFactory() { + return clientFactory; + } + + public SampleService getSampleService() { + return sampleService; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public void shutdown() throws Throwable { + } + +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/LowOrderBeanPostProcessor.java b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/LowOrderBeanPostProcessor.java new file mode 100644 index 000000000..60c2af476 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/java/com/alipay/sofa/runtime/integration/postprocessor/LowOrderBeanPostProcessor.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 com.alipay.sofa.runtime.integration.postprocessor; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * + * @author ruoshan + * @since 2.6.0 + */ +public class LowOrderBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof BeanPostProcessorTestBean) { + bean = new BeanPostProcessorTestBean(true); + } + return bean; + } +} \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/META-INF/async/async-test.xml b/runtime-sofa-boot-starter/src/test/resources/META-INF/async/async-test.xml new file mode 100644 index 000000000..d96ec42e4 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/resources/META-INF/async/async-test.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/META-INF/extension/extension-xmap.xml b/runtime-sofa-boot-starter/src/test/resources/META-INF/extension/extension-xmap.xml new file mode 100755 index 000000000..bb3399830 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/resources/META-INF/extension/extension-xmap.xml @@ -0,0 +1,5 @@ + + xmaptest + test1 + test2 + \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/META-INF/extension/extension.xml b/runtime-sofa-boot-starter/src/test/resources/META-INF/extension/extension.xml new file mode 100755 index 000000000..bcb9560df --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/resources/META-INF/extension/extension.xml @@ -0,0 +1,3 @@ + + SOFABoot Extension Client Test + \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-extension.xml b/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-extension.xml new file mode 100644 index 000000000..1f1c26800 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-extension.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + SOFABoot Extension Test + + value with path + + 10 + 20 + 1.1 + 2.2 + true + 17-01-2019 + file + http://test + com.alipay.sofa.runtime.integration.extension.descriptor.SimpleExtensionDescriptor + META-INF/extension/extension.xml + + + + + + + + + + + + + test1 + test2 + + attributeTest1 + attributeTest2 + + 1 + 2 + 11 + 22 + 1.1 + 2.2 + 11.11 + 22.22 + true + false + a + b + 33 + 44 + 7 + 8 + + + + + + + + + + + + + testMapValue1 + testMapValue2 + + + test + + + + value1 + value2 + + + + + + + + + + + + + + + + simpleSpringBean + + + + + + + + + + + + + + + + simpleSpringListBean1 + simpleSpringListBean2 + + + + + + + + + + + + + + + springMapBean1 + springMapBean2 + + value1 + value2 + + + + + + + + + + + + + + testContextValue + + + + + + + + + + + + + testParentValue + + + + + + + + + + + + + + + bad + + + + + \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-postprocessor-order.xml b/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-postprocessor-order.xml new file mode 100644 index 000000000..db5907844 --- /dev/null +++ b/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-postprocessor-order.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-service.xml b/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-service.xml index c7a05f419..97088ebda 100644 --- a/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-service.xml +++ b/runtime-sofa-boot-starter/src/test/resources/META-INF/spring/test-service.xml @@ -31,4 +31,8 @@ + + + + \ No newline at end of file diff --git a/runtime-sofa-boot-starter/src/test/resources/config/application.properties b/runtime-sofa-boot-starter/src/test/resources/config/application.properties index 86e431487..36e2852eb 100644 --- a/runtime-sofa-boot-starter/src/test/resources/config/application.properties +++ b/runtime-sofa-boot-starter/src/test/resources/config/application.properties @@ -1 +1,4 @@ spring.application.name=runtime-test +mix-xml-annotation-unique-id=xmlAnnotationSampleService + +logging.path=./logs \ No newline at end of file