You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shenyu.apache.org by he...@apache.org on 2026/03/30 13:30:10 UTC
(shenyu-plugin-store) 01/01: [type:init] add motan plugin
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 <he...@apache.org>
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:dev@shenyu.apache.org)
+
+---
+
+# 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: notifications@shenyu.apache.org
+ issues: notifications@shenyu.apache.org
+ pullrequests: notifications@shenyu.apache.org
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>dev@shenyu.apache.org</post>
+ <subscribe>dev-subscribe@shenyu.apache.org</subscribe>
+ <unsubscribe>dev-unsubscribe@shenyu.apache.org</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