This is an automated email from the ASF dual-hosted git repository. hefengen pushed a commit to branch check-plugin-center in repository https://gitbox.apache.org/repos/asf/shenyu-plugin-store.git
commit 2ef11f38abedc1a909a4e453a9d33136294e71e8 Author: moremind <[email protected]> AuthorDate: Mon Mar 30 20:49:53 2026 +0800 [type:init] add motan plugin --- .gitignore | 52 +++ README.md | 225 ++++++++++ asf.yaml | 41 ++ pom.xml | 124 ++++++ shenyu-plugin/pom.xml | 34 ++ shenyu-plugin/shenyu-plugin-proxy/pom.xml | 37 ++ .../shenyu-plugin-motan/pom.xml | 55 +++ .../apache/shenyu/plugin/motan/MotanPlugin.java | 129 ++++++ .../plugin/motan/cache/ApplicationConfigCache.java | 480 +++++++++++++++++++++ .../motan/context/MotanShenyuContextDecorator.java | 43 ++ .../plugin/motan/handler/MotanMetaDataHandler.java | 74 ++++ .../motan/handler/MotanPluginDataHandler.java | 79 ++++ .../plugin/motan/proxy/MotanProxyService.java | 197 +++++++++ .../shenyu/plugin/motan/util/PrxInfoUtil.java | 120 ++++++ .../shenyu/plugin/motan/MotanPluginTest.java | 109 +++++ .../motan/cache/ApplicationConfigCacheTest.java | 92 ++++ .../context/MotanShenyuContextDecoratorTest.java | 57 +++ .../motan/handler/MotanPluginDataHandlerTest.java | 54 +++ .../plugin/motan/proxy/MotanProxyServiceTest.java | 98 +++++ .../shenyu/plugin/motan/util/PrxInfoUtilTest.java | 32 ++ shenyu-spring-boot-starter-plugin/pom.xml | 34 ++ 21 files changed, 2166 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed771e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# maven ignore +target/ +*.class +*.jar +*.war +*.zip +*.tar +*.tar.gz +.flattened-pom.xml +dependency-reduced-pom.xml + +# maven plugin ignore +release.properties +cobertura.ser +*.gpg + +# eclipse ignore +.settings/ +.project +.classpath + +# idea ignore +.idea/ +!/.idea/icon.svg +!/.idea/vcs.xml +*.ipr +*.iml +*.iws + +# temp ignore +logs/ +*.log +*.doc +*.cache +*.diff +*.patch +*.tmp + +# system ignore +.DS_Store +Thumbs.db + +# agent build ignore +/agent/ + +# rust ignore +*.lock + +.factorypath + +# Private individual user cursor rules +.cursor/rules/_*.mdc diff --git a/README.md b/README.md new file mode 100644 index 0000000..8cdbd00 --- /dev/null +++ b/README.md @@ -0,0 +1,225 @@ + + + +<p align="center"> + <strong>Scalable, High Performance, Responsive API Gateway Solution for all MicroServices</strong> +</p> +<p align="center"> + <a href="https://shenyu.apache.org/">https://shenyu.apache.org/</a> +</p> + +<p align="center"> + <a href="https://shenyu.apache.org/docs/index" > + <img src="https://img.shields.io/badge/document-English-blue.svg" alt="EN docs" /> + </a> + <a href="https://shenyu.apache.org/zh/docs/index"> + <img src="https://img.shields.io/badge/文档-简体中文-blue.svg" alt="简体中文文档" /> + </a> +</p> + +<p align="center"> + <a target="_blank" href="https://search.maven.org/search?q=g:org.apache.shenyu%20AND%20a:shenyu"> + <img src="https://img.shields.io/maven-central/v/org.apache.shenyu/shenyu.svg?label=maven%20central" /> + </a> + <a target="_blank" href="https://github.com/apache/shenyu/blob/master/LICENSE"> + <img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg?label=license" /> + </a> + <a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html"> + <img src="https://img.shields.io/badge/JDK-17+-green.svg" /> + </a> + <a target="_blank" href="https://github.com/apache/shenyu/actions"> + <img src="https://github.com/apache/shenyu/workflows/ci/badge.svg" /> + </a> + <a target="_blank" href='https://github.com/apache/shenyu'> + <img src="https://img.shields.io/github/forks/apache/shenyu.svg" alt="github forks"/> + </a> + <a target="_blank" href='https://github.com/apache/shenyu'> + <img src="https://img.shields.io/github/stars/apache/shenyu.svg" alt="github stars"/> + </a> + <a target="_blank" href='https://github.com/apache/shenyu'> + <img src="https://img.shields.io/github/contributors/apache/shenyu.svg" alt="github contributors"/> + </a> + <a target="_blank" href="https://codecov.io/gh/apache/shenyu"> + <img src="https://codecov.io/gh/apache/shenyu/branch/master/graph/badge.svg" /> + </a> + <a target="_blank" href="https://hub.docker.com/r/apache/shenyu-bootstrap/tags"> + <image src="https://img.shields.io/docker/pulls/apache/shenyu-bootstrap" alt="Docker Pulls"/> + </a> + <a target="_blank" href="https://gitpod.io/#https://github.com/apache/shenyu"> + <image src="https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod&color=green"/> + </a> + <a target="_blank" href="https://deepwiki.com/apache/shenyu"> + <img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"> + </a> +</p> +<br/> + +--- + +# Architecture + +  + +---- + +# Why named Apache ShenYu + +ShenYu (神禹) is the honorific name of Chinese ancient monarch Xia Yu (also known in later times as Da Yu), +who left behind the touching story of the three times he crossed the Yellow River for the benefit of the people and successfully managed the flooding of the river. +He is known as one of the three greatest kings of ancient China, along with Yao and Shun. + + * Firstly, the name ShenYu is to promote the traditional virtues of our Chinese civilisation. + + * Secondly, the most important thing about the gateway is the governance of the traffic. + + * Finally, the community will do things in a fair, just, open and meritocratic way, paying tribute to ShenYu while also conforming to the Apache Way. + +--- + +# Features + +* Proxy: Support for Apache® Dubbo™, Spring Cloud, gRPC, Motan, SOFA, TARS, WebSocket, MQTT +* Security: Sign, OAuth 2.0, JSON Web Tokens, WAF plugin +* API governance: Request, response, parameter mapping, Hystrix, RateLimiter plugin +* Observability: Tracing, metrics, logging plugin +* Dashboard: Dynamic traffic control, visual backend for user menu permissions +* Extensions: Plugin hot-swapping, dynamic loading +* Cluster: NGINX, Docker, Kubernetes +* Language: provides .NET, Python, Go, Java client for API register + +--- + +# Quick Start (docker) + +### Create network for Shenyu + +``` +> docker network create shenyu +``` + +### Run Apache ShenYu Admin + +``` +> docker pull apache/shenyu-admin +> docker run -d --name shenyu-admin-quickstart -p 9095:9095 --net shenyu apache/shenyu-admin +``` + +### Run Apache ShenYu Bootstrap + +``` +> docker pull apache/shenyu-bootstrap +> docker run -d --name shenyu-quickstart -p 9195:9195 -e "shenyu.local.enabled=true" -e SHENYU_SYNC_WEBSOCKET_URLS=ws://shenyu-admin-quickstart:9095/websocket --net shenyu apache/shenyu-bootstrap +``` + +### Set router + +* Real request :http://127.0.0.1:8080/helloworld, + +```json +{ + "name" : "Shenyu", + "data" : "hello world" +} +``` + +* Set routing rules (Standalone) + +Add `localKey: 123456` to Headers. If you need to customize the localKey, you can use the sha512 tool to generate the key based on plaintext and update the `shenyu.local.sha512Key` property. + +``` +curl --location --request POST 'http://localhost:9195/shenyu/plugin/selectorAndRules' \ +--header 'Content-Type: application/json' \ +--header 'localKey: 123456' \ +--data-raw '{ + "pluginName": "divide", + "selectorHandler": "[{\"upstreamUrl\":\"127.0.0.1:8080\"}]", + "conditionDataList": [{ + "paramType": "uri", + "operator": "match", + "paramValue": "/**" + }], + "ruleDataList": [{ + "ruleHandler": "{\"loadBalance\":\"random\"}", + "conditionDataList": [{ + "paramType": "uri", + "operator": "match", + "paramValue": "/**" + }] + }] +}' +``` +> If the backend service handling the request is running on your host machine, please set `upstreamUrl` to `host.docker.internal:8080` or specify IP address if reachable from the container in the above command. +> +> Add `--network host` to docker run command instead of `--net shenyu` also works correctly. +* Proxy request :http://localhost:9195/helloworld + +```json +{ + "name" : "Shenyu", + "data" : "hello world" +} +``` +--- + +# Plugin + + Whenever a request comes in, Apache ShenYu will execute it by all enabled plugins through the chain of responsibility. + + As the heart of Apache ShenYu, plugins are extensible and hot-pluggable. + + Different plugins do different things. + + Of course, users can also customize plugins to meet their own needs. + + If you want to customize, see [custom-plugin](https://shenyu.apache.org/docs/developer/custom-plugin/) . + +--- + +# Selector & Rule + + According to your HTTP request headers, selectors and rules are used to route your requests. + + Selector is your first route, It is coarser grained, for example, at the module level. + + Rule is your second route and what do you think your request should do. For example a method level in a module. + + The selector and the rule match only once, and the match is returned. So the coarsest granularity should be sorted last. + +--- + +# Data Caching & Data Sync + + Since all data have been cached using ConcurrentHashMap in the JVM, it's very fast. + + Apache ShenYu dynamically updates the cache by listening to the ZooKeeper node (or WebSocket push, HTTP long polling) when the user changes configuration information in the background management. + +  + +  + +--- + +# Prerequisite + + * JDK 17+ + +--- + +# Stargazers over time + +[](https://starchart.cc/apache/shenyu.svg) + +--- + +# Contributor and Support + +* [How to Contribute](https://shenyu.apache.org/community/contributor-guide) +* [Mailing Lists](mailto:[email protected]) + +--- + +# Known Users + +In order of registration, More access companies are welcome to register at [https://github.com/apache/shenyu/issues/68](https://github.com/apache/shenyu/issues/68) (For open source users only) . + +All Users : [Known Users](https://shenyu.apache.org/community/user-registration) diff --git a/asf.yaml b/asf.yaml new file mode 100644 index 0000000..1936650 --- /dev/null +++ b/asf.yaml @@ -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. +# + +github: + description: Apache ShenYu Plugin Store. + homepage: https://shenyu.apache.org/ + labels: + - shenyu + features: + wiki: true + issues: true + projects: true + enabled_merge_buttons: + squash: true + merge: false + rebase: false + protected_branches: + main: + required_status_checks: + strict: true + required_pull_request_reviews: + dismiss_stale_reviews: true + required_approving_review_count: 1 +notifications: + commits: [email protected] + issues: [email protected] + pullrequests: [email protected] diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..35a2433 --- /dev/null +++ b/pom.xml @@ -0,0 +1,124 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache</groupId> + <artifactId>apache</artifactId> + <version>21</version> + </parent> + <modules> + <module>shenyu-plugin</module> + <module>shenyu-spring-boot-starter-plugin</module> + <module>shenyu-plugin/shenyu-plugin-proxy</module> + </modules> + <modelVersion>4.0.0</modelVersion> + <groupId>org.apache.shenyu</groupId> + <artifactId>shenyu-plugin-store</artifactId> + <packaging>pom</packaging> + <version>2.7.1-SNAPSHOT</version> + <name>shenyu-plugin-store</name> + + <licenses> + <license> + <name>Apache License, Version 2.0</name> + <url>https://www.apache.org/licenses/LICENSE-2.0</url> + <distribution>repo</distribution> + </license> + </licenses> + + <scm> + <url>https://github.com/apache/shenyu</url> + <connection>scm:git:https://github.com/apache/shenyu.git</connection> + <developerConnection>scm:git:https://github.com/apache/shenyu.git</developerConnection> + <tag>v2.5.0</tag> + </scm> + + <mailingLists> + <mailingList> + <name>Apache ShenYu Developer List</name> + <post>[email protected]</post> + <subscribe>[email protected]</subscribe> + <unsubscribe>[email protected]</unsubscribe> + </mailingList> + </mailingLists> + + <issueManagement> + <system>GitHub Issues</system> + <url>https://github.com/apache/shenyu/issues</url> + </issueManagement> + + <properties> + <java.version>17</java.version> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <skipTests>false</skipTests> + <!-- maven plugin version start --> + <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version> + <jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version> + <nexus-staging-maven-plugin.version>1.6.3</nexus-staging-maven-plugin.version> + <maven-gpg-plugin.version>1.6</maven-gpg-plugin.version> + <maven-source-plugin.version>3.0.1</maven-source-plugin.version> + <maven-surefire-plugin.version>3.0.0-M4</maven-surefire-plugin.version> + <maven-javadoc-plugin.version>3.6.0</maven-javadoc-plugin.version> + <maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version> + <maven-resources-plugin.version>3.2.0</maven-resources-plugin.version> + <maven-release-plugin.version>2.5.3</maven-release-plugin.version> + <versions-maven-plugin.version>2.5</versions-maven-plugin.version> + <maven-checkstyle-plugin.version>3.4.0</maven-checkstyle-plugin.version> + <apache-rat-plugin.version>0.15</apache-rat-plugin.version> + <frontend-maven-plugin.version>1.6</frontend-maven-plugin.version> + <frontend-maven-plugin.node.version>v12.14.1</frontend-maven-plugin.node.version> + <maven-shade-plugin.version>3.5.1</maven-shade-plugin.version> + <docker-maven-plugin.version>0.40.1</docker-maven-plugin.version> + <maven-assembly-plugin.version>3.5.0</maven-assembly-plugin.version> + <!-- maven plugin version end --> + + <!-- dependency version start --> + <motan.version>1.2.1</motan.version> + <log4j-1.2-api.vetsion>2.17.2</log4j-1.2-api.vetsion> + <!-- dependency version end --> + </properties> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-core</artifactId> + <version>${motan.version}</version> + </dependency> + + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-transport-netty4</artifactId> + <version>${motan.version}</version> + </dependency> + + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-registry-zookeeper</artifactId> + <version>${motan.version}</version> + </dependency> + + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-springsupport</artifactId> + <version>${motan.version}</version> + </dependency> + </dependencies> + </dependencyManagement> + +</project> diff --git a/shenyu-plugin/pom.xml b/shenyu-plugin/pom.xml new file mode 100644 index 0000000..d44cc5b --- /dev/null +++ b/shenyu-plugin/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.apache.shenyu</groupId> + <artifactId>shenyu-plugin-store</artifactId> + <version>2.7.1-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <packaging>pom</packaging> + <artifactId>shenyu-plugin</artifactId> + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + +</project> \ No newline at end of file diff --git a/shenyu-plugin/shenyu-plugin-proxy/pom.xml b/shenyu-plugin/shenyu-plugin-proxy/pom.xml new file mode 100644 index 0000000..79deb12 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/pom.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache.shenyu</groupId> + <artifactId>shenyu-plugin-store</artifactId> + <version>2.7.1-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>shenyu-plugin-proxy</artifactId> + <packaging>pom</packaging> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + <modules> + <module>shenyu-plugin-motan</module> + </modules> + +</project> \ No newline at end of file diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/pom.xml b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/pom.xml new file mode 100644 index 0000000..9db0cc2 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/pom.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache.shenyu</groupId> + <artifactId>shenyu-plugin-proxy</artifactId> + <version>2.7.1-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <artifactId>shenyu-plugin-motan</artifactId> + + <dependencies> + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-transport-netty4</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-registry-zookeeper</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-1.2-api</artifactId> + <version>${log4j-1.2-api.vetsion}</version> + </dependency> + <dependency> + <groupId>com.weibo</groupId> + <artifactId>motan-springsupport</artifactId> + <scope>provided</scope> + </dependency> + </dependencies> +</project> diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/MotanPlugin.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/MotanPlugin.java new file mode 100644 index 0000000..43c20d6 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/MotanPlugin.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shenyu.common.constant.Constants; +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.dto.RuleData; +import org.apache.shenyu.common.dto.SelectorData; +import org.apache.shenyu.common.enums.PluginEnum; +import org.apache.shenyu.common.enums.RpcTypeEnum; +import org.apache.shenyu.plugin.api.ShenyuPluginChain; +import org.apache.shenyu.plugin.api.context.ShenyuContext; +import org.apache.shenyu.plugin.api.result.ShenyuResultEnum; +import org.apache.shenyu.plugin.api.result.ShenyuResultWrap; +import org.apache.shenyu.plugin.api.utils.RequestUrlUtils; +import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils; +import org.apache.shenyu.plugin.base.AbstractShenyuPlugin; +import org.apache.shenyu.plugin.motan.proxy.MotanProxyService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Objects; + +/** + * Motan plugin. + */ +public class MotanPlugin extends AbstractShenyuPlugin { + + private static final Logger LOG = LoggerFactory.getLogger(MotanPlugin.class); + + private final MotanProxyService motanProxyService; + + /** + * Instantiates a new motan plugin. + * + * @param motanProxyService the motan proxy service + */ + public MotanPlugin(final MotanProxyService motanProxyService) { + this.motanProxyService = motanProxyService; + } + + @Override + protected String getRawPath(final ServerWebExchange exchange) { + return RequestUrlUtils.getRewrittenRawPath(exchange); + } + + @Override + @SuppressWarnings("all") + protected Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, + final SelectorData selector, final RuleData rule) { + String param = exchange.getAttribute(Constants.PARAM_TRANSFORM); + ShenyuContext shenyuContext = exchange.getAttribute(Constants.CONTEXT); + Objects.requireNonNull(shenyuContext); + MetaData metaData = exchange.getAttribute(Constants.META_DATA); + if (!checkMetaData(metaData)) { + Objects.requireNonNull(metaData); + LOG.error("path is :{}, meta data have error.... {}", shenyuContext.getPath(), metaData); + exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + Object error = ShenyuResultWrap.error(exchange, ShenyuResultEnum.META_DATA_ERROR); + return WebFluxResultUtils.result(exchange, error); + } + if (StringUtils.isNoneBlank(metaData.getParameterTypes()) && StringUtils.isBlank(param)) { + exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); + Object error = ShenyuResultWrap.error(exchange, ShenyuResultEnum.MOTAN_HAVE_BODY_PARAM); + return WebFluxResultUtils.result(exchange, error); + } + final Mono<Object> result = motanProxyService.genericInvoker(param, metaData, exchange, selector); + return result.then(chain.execute(exchange)); + } + + /** + * acquire plugin name. + * + * @return plugin name. + */ + @Override + public String named() { + return PluginEnum.MOTAN.getName(); + } + + /** + * plugin is execute. + * + * @param exchange the current server exchange + * @return default false. + */ + @Override + public boolean skip(final ServerWebExchange exchange) { + return skipExcept(exchange, RpcTypeEnum.MOTAN); + } + + @Override + protected Mono<Void> handleSelectorIfNull(final String pluginName, final ServerWebExchange exchange, final ShenyuPluginChain chain) { + return WebFluxResultUtils.noSelectorResult(pluginName, exchange); + } + + @Override + protected Mono<Void> handleRuleIfNull(final String pluginName, final ServerWebExchange exchange, final ShenyuPluginChain chain) { + return WebFluxResultUtils.noRuleResult(pluginName, exchange); + } + + @Override + public int getOrder() { + return PluginEnum.MOTAN.getCode(); + } + + private boolean checkMetaData(final MetaData metaData) { + return Objects.nonNull(metaData) && !StringUtils.isBlank(metaData.getMethodName()) && !StringUtils.isBlank(metaData.getServiceName()); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/cache/ApplicationConfigCache.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/cache/ApplicationConfigCache.java new file mode 100644 index 0000000..59e54d0 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/cache/ApplicationConfigCache.java @@ -0,0 +1,480 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.cache; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Maps; +import com.weibo.api.motan.config.ProtocolConfig; +import com.weibo.api.motan.config.RefererConfig; +import com.weibo.api.motan.config.RegistryConfig; +import com.weibo.api.motan.proxy.CommonClient; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.shenyu.common.constant.Constants; +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.dto.convert.plugin.MotanRegisterConfig; +import org.apache.shenyu.common.dto.convert.selector.MotanUpstream; +import org.apache.shenyu.common.exception.ShenyuException; +import org.apache.shenyu.common.utils.DigestUtils; +import org.apache.shenyu.common.utils.GsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.NonNull; + +import java.lang.reflect.Field; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +/** + * The cache info. + */ +public final class ApplicationConfigCache { + + private static final Logger LOG = LoggerFactory.getLogger(ApplicationConfigCache.class); + + private static final Map<String, MotanUpstream> UPSTREAM_CACHE_MAP = Maps.newConcurrentMap(); + + private RegistryConfig registryConfig; + + private ProtocolConfig protocolConfig; + + private final LoadingCache<String, RefererConfig<CommonClient>> cache = CacheBuilder.newBuilder().maximumSize(Constants.CACHE_MAX_COUNT).removalListener(notification -> { + RefererConfig<?> config = (RefererConfig<?>) notification.getValue(); + if (Objects.nonNull(config)) { + try { + Field field = FieldUtils.getDeclaredField(config.getClass(), "ref", true); + field.set(config, null); + // After the configuration change, motan destroys the instance, but does not empty it. If it is not handled, + // it will get NULL when reinitializing and cause a NULL pointer problem. + } catch (NullPointerException | IllegalAccessException e) { + LOG.error("modify ref have exception", e); + } + } + }).build(new CacheLoader<>() { + @Override + @NonNull + public RefererConfig<CommonClient> load(@NonNull final String key) { + return new RefererConfig<>(); + } + }); + + private ApplicationConfigCache() { + } + + /** + * Gets instance. + * + * @return the instance + */ + public static ApplicationConfigCache getInstance() { + return ApplicationConfigCacheInstance.INSTANCE; + } + + /** + * Init. + * + * @param motanRegisterConfig the motan register config + */ + public void init(final MotanRegisterConfig motanRegisterConfig) { + if (Objects.isNull(registryConfig)) { + registryConfig = new RegistryConfig(); + registryConfig.setId("shenyu_motan_proxy"); + registryConfig.setRegister(false); + } + registryConfig.setRegProtocol(motanRegisterConfig.getRegisterProtocol()); + registryConfig.setAddress(motanRegisterConfig.getRegisterAddress()); + if (Objects.isNull(protocolConfig)) { + protocolConfig = new ProtocolConfig(); + protocolConfig.setId("motan2"); + protocolConfig.setName("motan2"); + } + } + + /** + * Get reference config. + * + * @param <T> the type parameter + * @param path path + * @return the reference config + */ + @SuppressWarnings("unchecked") + public <T> RefererConfig<T> get(final String path) { + try { + return (RefererConfig<T>) cache.get(path); + } catch (ExecutionException e) { + throw new ShenyuException(e); + } + } + + /** + * Init ref reference config. + * + * @param metaData the meta data + * @return the reference config + */ + public RefererConfig<CommonClient> initRef(final MetaData metaData) { + try { + RefererConfig<CommonClient> referenceConfig = cache.get(metaData.getPath()); + if (StringUtils.isNoneBlank(referenceConfig.getServiceInterface())) { + return referenceConfig; + } + } catch (ExecutionException e) { + LOG.error("init motan ref ex:{}", e.getMessage()); + } + return build(metaData); + + } + + /** + * Init ref reference config. + * + * @param selectorId the select id + * @param metaData the meta data + * @param motanUpstream the motan upstream + * @return the reference config + */ + public RefererConfig<CommonClient> initRef(final String selectorId, final MetaData metaData, final MotanUpstream motanUpstream) { + try { + setUpstream(selectorId, motanUpstream); + RefererConfig<CommonClient> referenceConfig = cache.get(generateUpstreamCacheKey(selectorId, metaData.getPath(), motanUpstream)); + if (StringUtils.isNoneBlank(referenceConfig.getServiceInterface())) { + return referenceConfig; + } + } catch (ExecutionException e) { + LOG.error("init motan ref ex:{}", e.getMessage()); + } + return build(selectorId, metaData, motanUpstream); + + } + + /** + * Build reference config. + * + * @param metaData the meta data + * @return the reference config + */ + public RefererConfig<CommonClient> build(final MetaData metaData) { + if (Objects.isNull(protocolConfig) || Objects.isNull(registryConfig)) { + return new RefererConfig<>(); + } + RefererConfig<CommonClient> reference = new RefererConfig<>(); + reference.setInterface(CommonClient.class); + reference.setServiceInterface(metaData.getServiceName()); + // the group of motan rpc call + MotanParamExtInfo motanParamExtInfo = GsonUtils.getInstance().fromJson(metaData.getRpcExt(), MotanParamExtInfo.class); + reference.setGroup(motanParamExtInfo.getGroup()); + reference.setVersion("1.0"); + reference.setRequestTimeout(Optional.ofNullable(motanParamExtInfo.getTimeout()).orElse(1000)); + reference.setRegistry(registryConfig); + if (StringUtils.isNotEmpty(motanParamExtInfo.getRpcProtocol())) { + protocolConfig.setName(motanParamExtInfo.getRpcProtocol()); + protocolConfig.setId(motanParamExtInfo.getRpcProtocol()); + } + reference.setProtocol(protocolConfig); + CommonClient obj = reference.getRef(); + if (Objects.nonNull(obj)) { + LOG.info("init motan reference success there meteData is :{}", metaData); + cache.put(metaData.getPath(), reference); + } + return reference; + } + + /** + * Build reference config. + * + * @param selectorId the select id + * @param metaData the meta data + * @param motanUpstream the motan upstream + * @return the reference config + */ + public RefererConfig<CommonClient> build(final String selectorId, final MetaData metaData, final MotanUpstream motanUpstream) { + if (Objects.isNull(protocolConfig) || Objects.isNull(registryConfig)) { + return new RefererConfig<>(); + } + if (Objects.isNull(motanUpstream)) { + return this.build(metaData); + } + RefererConfig<CommonClient> reference = new RefererConfig<>(); + reference.setInterface(CommonClient.class); + reference.setServiceInterface(metaData.getServiceName()); + // the group of motan rpc call + MotanParamExtInfo motanParamExtInfo = GsonUtils.getInstance().fromJson(metaData.getRpcExt(), MotanParamExtInfo.class); + reference.setGroup(motanParamExtInfo.getGroup()); + reference.setVersion("1.0"); + reference.setRequestTimeout(Optional.ofNullable(motanParamExtInfo.getTimeout()).orElse(1000)); + RegistryConfig registryConfig = new RegistryConfig(); + registryConfig.setId("shenyu_motan_proxy"); + registryConfig.setRegister(false); + if (StringUtils.isNoneBlank(motanUpstream.getRegisterProtocol()) + && StringUtils.isNoneBlank(motanUpstream.getRegisterAddress())) { + Optional.ofNullable(motanUpstream.getRegisterProtocol()).ifPresent(registryConfig::setRegProtocol); + Optional.ofNullable(motanUpstream.getRegisterAddress()).ifPresent(registryConfig::setAddress); + } + reference.setRegistry(registryConfig); + if (StringUtils.isNotEmpty(motanParamExtInfo.getRpcProtocol())) { + protocolConfig.setName(motanParamExtInfo.getRpcProtocol()); + protocolConfig.setId(motanParamExtInfo.getRpcProtocol()); + } + reference.setProtocol(protocolConfig); + CommonClient obj = reference.getRef(); + if (Objects.nonNull(obj)) { + LOG.info("init motan reference success there meteData is :{}", metaData); + cache.put(generateUpstreamCacheKey(selectorId, metaData.getPath(), motanUpstream), reference); + } + return reference; + } + + /** + * generate motan upstream reference cache key. + * + * @param selectorId selectorId + * @param metaDataPath metaDataPath + * @param motanUpstream dubboUpstream + * @return the reference config cache key + */ + public String generateUpstreamCacheKey(final String selectorId, final String metaDataPath, final MotanUpstream motanUpstream) { + StringJoiner stringJoiner = new StringJoiner(Constants.SEPARATOR_UNDERLINE); + stringJoiner.add(selectorId); + stringJoiner.add(metaDataPath); + if (StringUtils.isNotBlank(motanUpstream.getProtocol())) { + stringJoiner.add(motanUpstream.getProtocol()); + } + // use registry hash to short reference cache key + if (StringUtils.isNotBlank(motanUpstream.getRegisterAddress())) { + String registryHash = DigestUtils.md5Hex(motanUpstream.getRegisterAddress()); + stringJoiner.add(registryHash); + } + return stringJoiner.toString(); + } + + /** + * get motanUpstream. + * + * @param path path + * @return motanUpstream + */ + public MotanUpstream getUpstream(final String path) { + return UPSTREAM_CACHE_MAP.get(path); + } + + /** + * set motanUpstream. + * + * @param path path + * @param motanUpstream motanUpstream + * @return motanUpstream + */ + public MotanUpstream setUpstream(final String path, final MotanUpstream motanUpstream) { + return UPSTREAM_CACHE_MAP.put(path, motanUpstream); + } + + /** + * Invalidate. + * + * @param path the path name + */ + public void invalidate(final String path) { + cache.invalidate(path); + } + + /** + * Invalidate all. + */ + public void invalidateAll() { + cache.invalidateAll(); + } + + /** + * Invalidate with metadataPath. + * + * @param metadataPath metadataPath + */ + public void invalidateWithMetadataPath(final String metadataPath) { + ConcurrentMap<String, RefererConfig<CommonClient>> map = cache.asMap(); + if (map.isEmpty()) { + return; + } + Set<String> allKeys = map.keySet(); + Set<String> needInvalidateKeys = allKeys.stream().filter(key -> key.contains(metadataPath)).collect(Collectors.toSet()); + if (needInvalidateKeys.isEmpty()) { + return; + } + needInvalidateKeys.forEach(cache::invalidate); + } + + /** + * invalidate with selectorId. + * + * @param selectorId selectorId + */ + public void invalidateWithSelectorId(final String selectorId) { + ConcurrentMap<String, RefererConfig<CommonClient>> map = cache.asMap(); + if (map.isEmpty()) { + return; + } + Set<String> allKeys = map.keySet(); + Set<String> needInvalidateKeys = allKeys.stream().filter(key -> key.contains(selectorId)).collect(Collectors.toSet()); + if (needInvalidateKeys.isEmpty()) { + return; + } + needInvalidateKeys.forEach(cache::invalidate); + } + + /** + * The type Application config cache instance. + */ + static final class ApplicationConfigCacheInstance { + + /** + * The Instance. + */ + static final ApplicationConfigCache INSTANCE = new ApplicationConfigCache(); + + private ApplicationConfigCacheInstance() { + + } + } + + /** + * The type Motan param ext info. + */ + static class MethodInfo { + + private String methodName; + + private List<Pair<String, String>> params; + + /** + * Gets method name. + * + * @return the method name + */ + public String getMethodName() { + return methodName; + } + + /** + * Sets method name. + * + * @param methodName the method name + */ + public void setMethodName(final String methodName) { + this.methodName = methodName; + } + + /** + * Gets params. + * + * @return the params + */ + public List<Pair<String, String>> getParams() { + return params; + } + + /** + * Sets params. + * + * @param params the params + */ + public void setParams(final List<Pair<String, String>> params) { + this.params = params; + } + } + + /** + * The type Motan param ext info. + */ + static class MotanParamExtInfo { + + private List<MethodInfo> methodInfo; + + private String group; + + private Integer timeout; + + private String rpcProtocol; + + /** + * Gets method info. + * + * @return the method info + */ + public List<MethodInfo> getMethodInfo() { + return methodInfo; + } + + /** + * Sets method info. + * + * @param methodInfo the method info + */ + public void setMethodInfo(final List<MethodInfo> methodInfo) { + this.methodInfo = methodInfo; + } + + /** + * Gets group. + * + * @return the group + */ + public String getGroup() { + return group; + } + + /** + * Sets group. + * + * @param group the group + */ + public void setGroup(final String group) { + this.group = group; + } + + public Integer getTimeout() { + return timeout; + } + + public void setTimeout(final Integer timeout) { + this.timeout = timeout; + } + + public String getRpcProtocol() { + return rpcProtocol; + } + + /** + * Sets rpc protocol. + * + * @param rpcProtocol the rpc protocol + */ + public void setRpcProtocol(final String rpcProtocol) { + this.rpcProtocol = rpcProtocol; + } + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/context/MotanShenyuContextDecorator.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/context/MotanShenyuContextDecorator.java new file mode 100644 index 0000000..7bb812e --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/context/MotanShenyuContextDecorator.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.context; + +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.enums.RpcTypeEnum; +import org.apache.shenyu.plugin.api.context.ShenyuContext; +import org.apache.shenyu.plugin.api.context.ShenyuContextDecorator; + +/** + * The type motan shenyu context decorator. + */ +public class MotanShenyuContextDecorator implements ShenyuContextDecorator { + + @Override + public ShenyuContext decorator(final ShenyuContext shenyuContext, final MetaData metaData) { + shenyuContext.setModule(metaData.getAppName()); + shenyuContext.setMethod(metaData.getServiceName()); + shenyuContext.setContextPath(metaData.getContextPath()); + shenyuContext.setRpcType(RpcTypeEnum.MOTAN.getName()); + return shenyuContext; + } + + @Override + public String rpcType() { + return RpcTypeEnum.MOTAN.getName(); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/handler/MotanMetaDataHandler.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/handler/MotanMetaDataHandler.java new file mode 100644 index 0000000..da47366 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/handler/MotanMetaDataHandler.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.handler; + +import com.google.common.collect.Maps; +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.enums.RpcTypeEnum; +import org.apache.shenyu.plugin.base.handler.MetaDataHandler; +import org.apache.shenyu.plugin.motan.cache.ApplicationConfigCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; +import java.util.concurrent.ConcurrentMap; + +/** + * The motan metadata handler. + */ +public class MotanMetaDataHandler implements MetaDataHandler { + + /** + * logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(MotanMetaDataHandler.class); + + private static final ConcurrentMap<String, MetaData> META_DATA = Maps.newConcurrentMap(); + + @Override + public void handle(final MetaData metaData) { + try { + MetaData exist = META_DATA.get(metaData.getPath()); + if (Objects.isNull(exist) || Objects.isNull(ApplicationConfigCache.getInstance().get(exist.getPath())) + || Objects.isNull(ApplicationConfigCache.getInstance().get(exist.getPath()).getRef())) { + // The first initialization + ApplicationConfigCache.getInstance().initRef(metaData); + } else { + if (!exist.getServiceName().equals(metaData.getServiceName()) || !exist.getRpcExt().equals(metaData.getRpcExt())) { + // update + ApplicationConfigCache.getInstance().invalidateWithMetadataPath(metaData.getPath()); + ApplicationConfigCache.getInstance().build(metaData); + } + } + META_DATA.put(metaData.getPath(), metaData); + } catch (Exception e) { + LOG.error("motan sync metadata is error, please check motan service. MetaData: [{}]", metaData, e); + } + } + + @Override + public void remove(final MetaData metaData) { + ApplicationConfigCache.getInstance().invalidateWithMetadataPath(metaData.getPath()); + META_DATA.remove(metaData.getPath()); + } + + @Override + public String rpcType() { + return RpcTypeEnum.MOTAN.getName(); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/handler/MotanPluginDataHandler.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/handler/MotanPluginDataHandler.java new file mode 100644 index 0000000..215d67c --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/handler/MotanPluginDataHandler.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 org.apache.shenyu.plugin.motan.handler; + +import org.apache.shenyu.common.dto.SelectorData; +import org.apache.shenyu.common.dto.convert.plugin.MotanRegisterConfig; +import org.apache.shenyu.common.dto.PluginData; +import org.apache.shenyu.common.dto.convert.selector.MotanUpstream; +import org.apache.shenyu.common.enums.PluginEnum; +import org.apache.shenyu.common.utils.GsonUtils; +import org.apache.shenyu.plugin.base.handler.PluginDataHandler; +import org.apache.shenyu.common.utils.Singleton; +import org.apache.shenyu.plugin.motan.cache.ApplicationConfigCache; + +import java.util.Objects; + +/** + * The type motan plugin data handler. + */ +public class MotanPluginDataHandler implements PluginDataHandler { + + @Override + public void handlerPlugin(final PluginData pluginData) { + if (Objects.nonNull(pluginData) && Boolean.TRUE.equals(pluginData.getEnabled())) { + MotanRegisterConfig motanRegisterConfig = GsonUtils.getInstance().fromJson(pluginData.getConfig(), MotanRegisterConfig.class); + MotanRegisterConfig exist = Singleton.INST.get(MotanRegisterConfig.class); + if (Objects.isNull(motanRegisterConfig)) { + return; + } + if (Objects.isNull(exist) || !motanRegisterConfig.equals(exist)) { + // If it is null, initialize it + ApplicationConfigCache.getInstance().init(motanRegisterConfig); + ApplicationConfigCache.getInstance().invalidateAll(); + } + Singleton.INST.single(MotanRegisterConfig.class, motanRegisterConfig); + } + } + + @Override + public void removePlugin(final PluginData pluginData) { + ApplicationConfigCache.getInstance().invalidateAll(); + } + + @Override + public void handlerSelector(final SelectorData selectorData) { + MotanUpstream motanUpstream = GsonUtils.getInstance().fromJson(selectorData.getHandle(), MotanUpstream.class); + if (Objects.equals(motanUpstream, ApplicationConfigCache + .getInstance().getUpstream(selectorData.getId()))) { + return; + } + ApplicationConfigCache.getInstance().invalidateWithSelectorId(selectorData.getId()); + ApplicationConfigCache.getInstance().setUpstream(selectorData.getId(), motanUpstream); + } + + @Override + public void removeSelector(final SelectorData selectorData) { + ApplicationConfigCache.getInstance().invalidateWithSelectorId(selectorData.getId()); + } + + @Override + public String pluginNamed() { + return PluginEnum.MOTAN.getName(); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/proxy/MotanProxyService.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/proxy/MotanProxyService.java new file mode 100644 index 0000000..8adcf60 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/proxy/MotanProxyService.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.proxy; + +import com.weibo.api.motan.config.RefererConfig; +import com.weibo.api.motan.proxy.CommonClient; +import com.weibo.api.motan.rpc.Request; +import com.weibo.api.motan.rpc.ResponseFuture; +import com.weibo.api.motan.rpc.RpcContext; +import com.weibo.api.motan.util.MotanClientUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.shenyu.common.concurrent.ShenyuThreadFactory; +import org.apache.shenyu.common.concurrent.ShenyuThreadPoolExecutor; +import org.apache.shenyu.common.constant.Constants; +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.dto.SelectorData; +import org.apache.shenyu.common.dto.convert.plugin.MotanRegisterConfig; +import org.apache.shenyu.common.dto.convert.selector.MotanUpstream; +import org.apache.shenyu.common.enums.PluginEnum; +import org.apache.shenyu.common.enums.ResultEnum; +import org.apache.shenyu.common.exception.ShenyuException; +import org.apache.shenyu.common.utils.GsonUtils; +import org.apache.shenyu.common.utils.ParamCheckUtils; +import org.apache.shenyu.common.utils.Singleton; +import org.apache.shenyu.plugin.api.utils.BodyParamUtils; +import org.apache.shenyu.plugin.api.utils.SpringBeanUtils; +import org.apache.shenyu.plugin.motan.cache.ApplicationConfigCache; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.LinkedBlockingQueue; + + +/** + * Motan proxy service. + */ +public class MotanProxyService { + + private static final Logger LOG = LoggerFactory.getLogger(MotanProxyService.class); + + private final ThreadFactory factory = ShenyuThreadFactory.create("shenyu-motan", true); + + private ExecutorService threadPool; + + /** + * Generic invoker object. + * + * @param body the body + * @param metaData the meta data + * @param exchange the exchange + * @param selectorData the selectorData + * @return the object + * @throws ShenyuException the shenyu exception + */ + @SuppressWarnings("all") + public Mono<Object> genericInvoker(final String body, final MetaData metaData, final ServerWebExchange exchange, final SelectorData selectorData) throws ShenyuException { + Map<String, Map<String, String>> rpcContext = exchange.getAttribute(Constants.GENERAL_CONTEXT); + Optional.ofNullable(rpcContext).map(context -> context.get(PluginEnum.MOTAN.getName())).ifPresent(context -> { + context.forEach((k, v) -> RpcContext.getContext().setRpcAttachment(k, v)); + }); + RefererConfig<CommonClient> reference = getConsumerConfig(selectorData, metaData); + if (Objects.isNull(reference) || StringUtils.isEmpty(reference.getServiceInterface())) { + ApplicationConfigCache.getInstance().invalidate(metaData.getPath()); + reference = ApplicationConfigCache.getInstance().initRef(metaData); + } + CommonClient commonClient = reference.getRef(); + Pair<String[], Object[]> pair; + if (StringUtils.isBlank(metaData.getParameterTypes()) || ParamCheckUtils.bodyIsEmpty(body)) { + pair = new ImmutablePair<>(new String[]{}, new Object[]{}); + } else { + pair = BodyParamUtils.buildParameters(body, metaData.getParameterTypes()); + } + ResponseFuture responseFuture; + //CHECKSTYLE:OFF IllegalCatch + try { + Request request = MotanClientUtil.buildRequest(reference.getServiceInterface(), metaData.getMethodName(), metaData.getParameterTypes(), pair.getRight(), null); + responseFuture = (ResponseFuture)commonClient.asyncCall(request, Object.class); + } catch (Throwable e) { + LOG.error("Exception caught in MotanProxyService#genericInvoker.", e); + return null; + } + //CHECKSTYLE:ON IllegalCatch + initThreadPool(); + CompletableFuture<Object> future = CompletableFuture.supplyAsync(responseFuture::getValue, threadPool); + return Mono.fromFuture(future.thenApply(ret -> { + Object result = ret; + if (Objects.isNull(result)) { + result = Constants.MOTAN_RPC_RESULT_EMPTY; + } + exchange.getAttributes().put(Constants.RPC_RESULT, result); + exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName()); + return result; + })).onErrorMap(ShenyuException::new); + } + + /** + * get motan reference config. + * + * @param selectorData the selector data + * @param metaData the meta data + * @return motan reference config + */ + public RefererConfig<CommonClient> getConsumerConfig(final SelectorData selectorData, final MetaData metaData) { + String referenceKey = metaData.getPath(); + MotanUpstream motanUpstream = GsonUtils.getInstance().fromJson(selectorData.getHandle(), MotanUpstream.class); + // if motanUpstream is empty, use default plugin config + if (Objects.isNull(motanUpstream)) { + RefererConfig<CommonClient> reference = ApplicationConfigCache.getInstance().get(referenceKey); + if (StringUtils.isBlank(reference.getServiceInterface())) { + ApplicationConfigCache.getInstance().invalidate(referenceKey); + reference = ApplicationConfigCache.getInstance().initRef(metaData); + } + return reference; + } + referenceKey = ApplicationConfigCache.getInstance().generateUpstreamCacheKey(selectorData.getId(), metaData.getPath(), motanUpstream); + RefererConfig<CommonClient> reference = ApplicationConfigCache.getInstance().get(referenceKey); + if (StringUtils.isBlank(reference.getServiceInterface())) { + ApplicationConfigCache.getInstance().invalidate(referenceKey); + reference = ApplicationConfigCache.getInstance().initRef(selectorData.getId(), metaData, motanUpstream); + } + return reference; + } + + private void initThreadPool() { + if (Objects.nonNull(threadPool)) { + return; + } + MotanRegisterConfig config = Singleton.INST.get(MotanRegisterConfig.class); + if (Objects.isNull(config)) { + // should not execute to here + threadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue<>(), + factory); + return; + } + final String threadpool = Optional.ofNullable(config.getThreadpool()).orElse(Constants.CACHED); + switch (threadpool) { + case Constants.SHARED: + try { + threadPool = SpringBeanUtils.getInstance().getBean(ShenyuThreadPoolExecutor.class); + return; + } catch (NoSuchBeanDefinitionException t) { + throw new ShenyuException("shared thread pool is not enable, config ${shenyu.sharedPool.enable} in your xml/yml !", t); + } + case Constants.FIXED: + case Constants.EAGER: + case Constants.LIMITED: + throw new UnsupportedOperationException(); + case Constants.CACHED: + default: + int corePoolSize = Optional.ofNullable(config.getCorethreads()).orElse(0); + int maximumPoolSize = Optional.ofNullable(config.getThreads()).orElse(Integer.MAX_VALUE); + int queueSize = Optional.ofNullable(config.getQueues()).orElse(0); + threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 60L, TimeUnit.SECONDS, + queueSize > 0 ? new LinkedBlockingQueue<>(queueSize) : new SynchronousQueue<>(), factory); + } + } + + /** + * get thread pool, just for integrated test. + * + * @return the thread pool + */ + public ExecutorService getThreadPool() { + return threadPool; + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/util/PrxInfoUtil.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/util/PrxInfoUtil.java new file mode 100644 index 0000000..26be85f --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/main/java/org/apache/shenyu/plugin/motan/util/PrxInfoUtil.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 org.apache.shenyu.plugin.motan.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Proxy info util. + */ +public final class PrxInfoUtil { + + private static final Map<String, PrimitiveType> PRIMITIVE_TYPE; + + static { + PRIMITIVE_TYPE = new HashMap<>(); + PRIMITIVE_TYPE.put("int", new PrimitiveType(int.class, o -> { + if (o instanceof String) { + return Integer.valueOf((String) o); + } + return ((Long) o).intValue(); + })); + PRIMITIVE_TYPE.put("double", new PrimitiveType(double.class, o -> { + if (o instanceof String) { + return Double.valueOf((String) o); + } + return o; + })); + PRIMITIVE_TYPE.put("long", new PrimitiveType(long.class, o -> { + if (o instanceof String) { + return Long.valueOf((String) o); + } + return o; + })); + PRIMITIVE_TYPE.put("short", new PrimitiveType(short.class, o -> { + if (o instanceof String) { + return Short.valueOf((String) o); + } + return ((Long) o).shortValue(); + })); + PRIMITIVE_TYPE.put("byte", new PrimitiveType(byte.class, o -> { + if (o instanceof String) { + return Byte.valueOf((String) o); + } + return ((Long) o).byteValue(); + })); + PRIMITIVE_TYPE.put("boolean", new PrimitiveType(boolean.class, o -> { + if (o instanceof String) { + return Byte.valueOf((String) o); + } + return o; + })); + PRIMITIVE_TYPE.put("char", new PrimitiveType(char.class, o -> { + if (o instanceof String) { + return String.valueOf(o).charAt(0); + } + return o; + })); + PRIMITIVE_TYPE.put("float", new PrimitiveType(float.class, o -> { + if (o instanceof String) { + return Float.valueOf((String) o); + } + return ((Double) o).floatValue(); + })); + } + + private PrxInfoUtil() { + } + + /** + * Get class type by name. + * + * @param className className + * @return the type to invoke + * @throws ClassNotFoundException ClassNotFoundException + */ + public static Class<?> getParamClass(final String className) throws ClassNotFoundException { + if (PRIMITIVE_TYPE.containsKey(className)) { + return PRIMITIVE_TYPE.get(className).getClazz(); + } else { + return Class.forName(className); + } + } + + static final class PrimitiveType { + + private final Class<?> clazz; + + private final Function<Object, Object> func; + + private PrimitiveType(final Class<?> clazz, final Function<Object, Object> func) { + this.clazz = clazz; + this.func = func; + } + + public Class<?> getClazz() { + return clazz; + } + + public Function<Object, Object> getFunc() { + return func; + } + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/MotanPluginTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/MotanPluginTest.java new file mode 100644 index 0000000..120e82b --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/MotanPluginTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan; + +import org.apache.shenyu.common.constant.Constants; +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.dto.RuleData; +import org.apache.shenyu.common.dto.SelectorData; +import org.apache.shenyu.plugin.api.ShenyuPluginChain; +import org.apache.shenyu.plugin.api.context.ShenyuContext; +import org.apache.shenyu.plugin.motan.proxy.MotanProxyService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; + +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +/** + * The Test Case For MotanPlugin. + */ +public final class MotanPluginTest { + + private static final String PARAM = "{\"name\":\"motan\"}"; + + private MotanPlugin motanPlugin; + + private RuleData ruleData; + + private ShenyuPluginChain chain; + + private SelectorData selectorData; + + private ServerWebExchange exchange; + + private MetaData metaData; + + private MotanProxyService motanProxyService; + + @BeforeEach + public void setUp() { + this.ruleData = mock(RuleData.class); + this.chain = mock(ShenyuPluginChain.class); + this.selectorData = mock(SelectorData.class); + this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("localhost").build()); + this.metaData = new MetaData(); + this.metaData.setAppName("motan"); + this.metaData.setContextPath("/motan"); + this.metaData.setPath("/motan/hello"); + this.metaData.setRpcType("motan"); + this.metaData.setServiceName("org.apache.shenyu.examples.motan.service.MotanDemoService"); + this.metaData.setMethodName("hello"); + this.metaData.setParameterTypes("java.lang.String"); + this.metaData.setEnabled(true); + metaData.setRpcExt("{\"methodInfo\":[{\"methodName\":\"hello\",\"params\":[{\"left\":\"java.lang.String\",\"right\":\"name\"}]}],\"group\":\"motan-shenyu-rpc\"}"); + ShenyuContext shenyuContext = mock(ShenyuContext.class); + exchange.getAttributes().put(Constants.CONTEXT, shenyuContext); + exchange.getAttributes().put(Constants.PARAM_TRANSFORM, PARAM); + exchange.getAttributes().put(Constants.META_DATA, metaData); + this.motanProxyService = mock(MotanProxyService.class); + when(motanProxyService.genericInvoker(PARAM, metaData, exchange, selectorData)).thenReturn(Mono.empty()); + this.motanPlugin = new MotanPlugin(motanProxyService); + } + + @Test + public void testDoExecute() { + when(chain.execute(exchange)).thenReturn(Mono.empty()); + Mono<Void> result = motanPlugin.doExecute(exchange, chain, selectorData, ruleData); + StepVerifier.create(result).expectSubscription().verifyComplete(); + } + + @Test + public void testNamed() { + Assertions.assertEquals(motanPlugin.named(), "motan"); + } + + @Test + public void testSkip() { + final boolean result = motanPlugin.skip(exchange); + Assertions.assertTrue(result); + } + + @Test + public void testGetOrder() { + Assertions.assertEquals(motanPlugin.getOrder(), 310); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/cache/ApplicationConfigCacheTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/cache/ApplicationConfigCacheTest.java new file mode 100644 index 0000000..a24ded7 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/cache/ApplicationConfigCacheTest.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.cache; + +import com.weibo.api.motan.config.ProtocolConfig; +import com.weibo.api.motan.config.RegistryConfig; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.shenyu.common.dto.PluginData; +import org.apache.shenyu.common.dto.convert.plugin.MotanRegisterConfig; +import org.apache.shenyu.common.utils.GsonUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * The Test Case For ApplicationConfigCache. + */ +public final class ApplicationConfigCacheTest { + + @Test + public void testMotanParamExtInfo() { + ApplicationConfigCache.MotanParamExtInfo motanParamExtInfo = new ApplicationConfigCache.MotanParamExtInfo(); + ApplicationConfigCache.MethodInfo methodInfo = new ApplicationConfigCache.MethodInfo(); + methodInfo.setMethodName("test"); + List<ApplicationConfigCache.MethodInfo> list = new ArrayList<>(); + list.add(methodInfo); + motanParamExtInfo.setMethodInfo(list); + motanParamExtInfo.setGroup("test"); + motanParamExtInfo.setTimeout(1000); + Assertions.assertEquals(motanParamExtInfo.getGroup(), "test"); + Assertions.assertEquals(motanParamExtInfo.getMethodInfo().get(0).getMethodName(), "test"); + Assertions.assertEquals(motanParamExtInfo.getTimeout(), 1000); + } + + @Test + public void testMethodInfo() { + List<Pair<String, String>> params = new ArrayList<>(); + Pair<String, String> pair = Pair.of("left", "right"); + params.add(pair); + ApplicationConfigCache.MethodInfo methodInfo = new ApplicationConfigCache.MethodInfo(); + methodInfo.setParams(params); + Assertions.assertEquals(methodInfo.getParams().get(0).getLeft(), "left"); + } + + @Test + public void testApplicationConfigCacheInstance() { + Assertions.assertEquals(ApplicationConfigCache.ApplicationConfigCacheInstance.INSTANCE.getClass(), ApplicationConfigCache.class); + } + + @Test + public void testApplicationConfigCache() throws NoSuchFieldException, IllegalAccessException { + ApplicationConfigCache applicationConfigCache = ApplicationConfigCache.getInstance(); + Assertions.assertEquals(applicationConfigCache.getInstance().getClass(), ApplicationConfigCache.class); + PluginData pluginData = new PluginData(); + pluginData.setEnabled(true); + pluginData.setConfig("{\"register\" : \"localhost:2181\"}"); + MotanRegisterConfig motanRegisterConfig = GsonUtils.getInstance().fromJson(pluginData.getConfig(), MotanRegisterConfig.class); + applicationConfigCache.init(motanRegisterConfig); + Field field1 = applicationConfigCache.getClass().getDeclaredField("registryConfig"); + field1.setAccessible(true); + RegistryConfig registryConfig = (RegistryConfig) field1.get(applicationConfigCache); + Assertions.assertEquals(registryConfig.getId(), "shenyu_motan_proxy"); + Field field2 = applicationConfigCache.getClass().getDeclaredField("protocolConfig"); + field2.setAccessible(true); + ProtocolConfig protocolConfig = (ProtocolConfig) field2.get(applicationConfigCache); + Assertions.assertEquals(protocolConfig.getId(), "motan2"); + } + + @Test + public void testGet() { + ApplicationConfigCache applicationConfigCache = ApplicationConfigCache.getInstance(); + Assertions.assertEquals(applicationConfigCache.get("/motan").toString(), "<motan:referer />"); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/context/MotanShenyuContextDecoratorTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/context/MotanShenyuContextDecoratorTest.java new file mode 100644 index 0000000..91c9654 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/context/MotanShenyuContextDecoratorTest.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 org.apache.shenyu.plugin.motan.context; + +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.plugin.api.context.ShenyuContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * The Test Case For MotanShenyuContextDecorator. + */ +public final class MotanShenyuContextDecoratorTest { + + private MotanShenyuContextDecorator motanShenyuContextDecorator; + + private ShenyuContext shenyuContext; + + private MetaData metaData; + + @BeforeEach + public void setUp() { + this.motanShenyuContextDecorator = new MotanShenyuContextDecorator(); + this.metaData = new MetaData(); + this.shenyuContext = new ShenyuContext(); + } + + @Test + public void testDecorator() { + metaData.setAppName("app"); + metaData.setServiceName("service"); + metaData.setContextPath("localhost"); + motanShenyuContextDecorator.decorator(shenyuContext, metaData); + Assertions.assertEquals(shenyuContext.getModule(), "app"); + } + + @Test + public void testRpcType() { + Assertions.assertEquals(motanShenyuContextDecorator.rpcType(), "motan"); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/handler/MotanPluginDataHandlerTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/handler/MotanPluginDataHandlerTest.java new file mode 100644 index 0000000..a9ca363 --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/handler/MotanPluginDataHandlerTest.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.handler; + +import org.apache.shenyu.common.dto.PluginData; +import org.apache.shenyu.common.dto.convert.plugin.MotanRegisterConfig; +import org.apache.shenyu.common.utils.Singleton; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * The Test Case For MotanPluginDataHandler. + */ +public final class MotanPluginDataHandlerTest { + + private MotanPluginDataHandler motanPluginDataHandler; + + private PluginData pluginData; + + @BeforeEach + public void setUp() { + this.motanPluginDataHandler = new MotanPluginDataHandler(); + this.pluginData = new PluginData(); + } + + @Test + public void testHandlerPlugin() { + pluginData.setEnabled(true); + pluginData.setConfig("{\"registerAddress\" : \"127.0.0.1:2181\"}"); + motanPluginDataHandler.handlerPlugin(pluginData); + Assertions.assertEquals(Singleton.INST.get(MotanRegisterConfig.class).getRegisterAddress(), "127.0.0.1:2181"); + } + + @Test + public void testPluginNamed() { + Assertions.assertEquals(motanPluginDataHandler.pluginNamed(), "motan"); + } +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/proxy/MotanProxyServiceTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/proxy/MotanProxyServiceTest.java new file mode 100644 index 0000000..196d56a --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/proxy/MotanProxyServiceTest.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 org.apache.shenyu.plugin.motan.proxy; + +import com.weibo.api.motan.config.RefererConfig; +import com.weibo.api.motan.proxy.CommonClient; +import com.weibo.api.motan.rpc.Request; +import com.weibo.api.motan.rpc.ResponseFuture; +import org.apache.shenyu.common.dto.MetaData; +import org.apache.shenyu.common.dto.SelectorData; +import org.apache.shenyu.common.dto.convert.plugin.MotanRegisterConfig; +import org.apache.shenyu.common.enums.RpcTypeEnum; +import org.apache.shenyu.plugin.motan.cache.ApplicationConfigCache; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +/** + * MotanProxyServiceTest. + */ +@ExtendWith(MockitoExtension.class) +public class MotanProxyServiceTest { + + private MetaData metaData; + + private ServerWebExchange exchange; + + @BeforeEach + public void setup() { + exchange = MockServerWebExchange.from(MockServerHttpRequest.get("localhost").build()); + metaData = new MetaData(); + metaData.setId("1332017966661636096"); + metaData.setAppName("sofa"); + metaData.setPath("/motan/findAll"); + metaData.setServiceName("org.apache.shenyu.test.motan.api.service.MotanTestService"); + metaData.setMethodName("findAll"); + metaData.setRpcType(RpcTypeEnum.MOTAN.getName()); + metaData.setRpcExt("{\"loadbalance\": \"loadbalance\"}"); + } + + @AfterEach + public void after() { + ApplicationConfigCache.getInstance().invalidateAll(); + } + + @Test + @SuppressWarnings("all") + public void testGenericInvoker() { + + ApplicationConfigCache.getInstance().init(new MotanRegisterConfig()); + SelectorData selectorData = mock(SelectorData.class); + + RefererConfig<CommonClient> reference = mock(RefererConfig.class); + CommonClient commonClient = mock(CommonClient.class); + when(reference.getRef()).thenReturn(commonClient); + when(reference.getServiceInterface()).thenReturn("org.apache.shenyu.test.motan.api.service.MotanTestService"); + + MotanProxyService motanProxyService = spy(new MotanProxyService()); + doReturn(reference).when(motanProxyService).getConsumerConfig(selectorData, metaData); + + ResponseFuture responseFuture = mock(ResponseFuture.class); + try { + when(commonClient.asyncCall(any(Request.class), eq(Object.class))).thenReturn(responseFuture); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + motanProxyService.genericInvoker("", metaData, exchange, selectorData); + } + +} diff --git a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/util/PrxInfoUtilTest.java b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/util/PrxInfoUtilTest.java new file mode 100644 index 0000000..5b7e60c --- /dev/null +++ b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-motan/src/test/java/org/apache/shenyu/plugin/motan/util/PrxInfoUtilTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.plugin.motan.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * The Test Case For PrxInfoUtil. + */ +public final class PrxInfoUtilTest { + + @Test + public void testStatic() throws ClassNotFoundException { + Assertions.assertEquals(PrxInfoUtil.getParamClass("int"), int.class); + } +} diff --git a/shenyu-spring-boot-starter-plugin/pom.xml b/shenyu-spring-boot-starter-plugin/pom.xml new file mode 100644 index 0000000..ab2e555 --- /dev/null +++ b/shenyu-spring-boot-starter-plugin/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.apache.shenyu</groupId> + <artifactId>shenyu-plugin-store</artifactId> + <version>2.7.1-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <packaging>pom</packaging> + <artifactId>shenyu-spring-boot-starter-plugin</artifactId> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + +</project> \ No newline at end of file
