From eb6cc9976af5a4aae01a78235d392648957c6f50 Mon Sep 17 00:00:00 2001 From: "nil.zhong" Date: Thu, 25 May 2023 15:36:21 +0800 Subject: [PATCH 01/14] Change version from 2.7.2 to 3.0.0-SNAPSHOT --- README.en-us.md | 15 ++++---- README.md | 59 ++++++++++++++++---------------- docker-compose.yml | 6 ++-- fizz-bootstrap/pom.xml | 2 +- fizz-common/pom.xml | 2 +- fizz-core/pom.xml | 2 +- fizz-plugin/pom.xml | 2 +- fizz-spring-boot-starter/pom.xml | 2 +- pom.xml | 2 +- 9 files changed, 47 insertions(+), 45 deletions(-) diff --git a/README.en-us.md b/README.en-us.md index 4717f02..a9b22eb 100644 --- a/README.en-us.md +++ b/README.en-us.md @@ -101,13 +101,14 @@ Starting from v1.3.0, the frontend and backend of the management backend are mer - fizz-manager-professional:Management backend professional version | fizz-gateway-node | fizz-manager-professional | -|------------------------|---------------------------| -| v1.3.0 | v1.3.0 | -| ... | ... | -| v2.6.6 | v2.6.6 | -| v2.7.0 | v2.7.0 | -| v2.7.1 | v2.7.1 | -| v2.7.2 | v2.7.2 | +|-------------------|---------------------------| +| v1.3.0 | v1.3.0 | +| ... | ... | +| v2.6.6 | v2.6.6 | +| v2.7.0 | v2.7.0 | +| v2.7.1 | v2.7.1 | +| v2.7.2 | v2.7.2 | +| v3.0.0-SNAPSHOT | v3.0.0-SNAPSHOT | Please download the corresponding management backend version according to the version of the community version diff --git a/README.md b/README.md index 2770c56..48ea0b7 100644 --- a/README.md +++ b/README.md @@ -103,35 +103,36 @@ API地址:http://demo.fizzgate.com/proxy/[服务名]/[API_Path] - fizz-manager-professional:管理后台 | fizz-gateway-node | fizz-manager-professional | -|------------------------|---------------------------| -| v1.3.0 | v1.3.0 | -| v1.4.0 | v1.4.0 | -| v1.4.1 | v1.4.1 | -| v1.5.0 | v1.5.0 | -| v1.5.1 | v1.5.1 | -| v2.0.0 | v2.0.0 | -| v2.1.0 | v2.1.0 | -| v2.2.0 | v2.2.0 | -| v2.2.1 | v2.2.1 | -| v2.2.3 | v2.2.3 | -| v2.3.0 | v2.3.0 | -| v2.3.2 | v2.3.2 | -| v2.3.3 | v2.3.3 | -| v2.4.0 | v2.4.0 | -| v2.4.1 | v2.4.1 | -| v2.5.0 | v2.5.0 | -| v2.5.1 | v2.5.1 | -| v2.5.2 | v2.5.2 | -| v2.6.0 | v2.6.0 | -| v2.6.1 | v2.6.1 | -| v2.6.2 | v2.6.2 | -| v2.6.3 | v2.6.3 | -| v2.6.4 | v2.6.4 | -| v2.6.5 | v2.6.5 | -| v2.6.6 | v2.6.6 | -| v2.7.0 | v2.7.0 | -| v2.7.1 | v2.7.1 | -| v2.7.2 | v2.7.2 | +|-------------------|---------------------------| +| v1.3.0 | v1.3.0 | +| v1.4.0 | v1.4.0 | +| v1.4.1 | v1.4.1 | +| v1.5.0 | v1.5.0 | +| v1.5.1 | v1.5.1 | +| v2.0.0 | v2.0.0 | +| v2.1.0 | v2.1.0 | +| v2.2.0 | v2.2.0 | +| v2.2.1 | v2.2.1 | +| v2.2.3 | v2.2.3 | +| v2.3.0 | v2.3.0 | +| v2.3.2 | v2.3.2 | +| v2.3.3 | v2.3.3 | +| v2.4.0 | v2.4.0 | +| v2.4.1 | v2.4.1 | +| v2.5.0 | v2.5.0 | +| v2.5.1 | v2.5.1 | +| v2.5.2 | v2.5.2 | +| v2.6.0 | v2.6.0 | +| v2.6.1 | v2.6.1 | +| v2.6.2 | v2.6.2 | +| v2.6.3 | v2.6.3 | +| v2.6.4 | v2.6.4 | +| v2.6.5 | v2.6.5 | +| v2.6.6 | v2.6.6 | +| v2.7.0 | v2.7.0 | +| v2.7.1 | v2.7.1 | +| v2.7.2 | v2.7.2 | +| v3.0.0-SNAPSHOT | v3.0.0-SNAPSHOT | 请根据节点端的版本下载对应的管理后台版本 diff --git a/docker-compose.yml b/docker-compose.yml index 3436910..1867a80 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.6" services: fizz-mysql: - image: "fizzgate/fizz-mysql:2.7.2" + image: "fizzgate/fizz-mysql:3.0.0-SNAPSHOT" container_name: fizz-mysql restart: always hostname: fizz-mysql @@ -27,7 +27,7 @@ services: - fizz fizz-gateway-community: - image: "fizzgate/fizz-gateway-community:2.7.2" + image: "fizzgate/fizz-gateway-community:3.0.0-SNAPSHOT" container_name: fizz-gateway-community restart: always hostname: fizz-gateway-community @@ -48,7 +48,7 @@ services: - fizz fizz-manager-professional: - image: "fizzgate/fizz-manager-professional:2.7.2" + image: "fizzgate/fizz-manager-professional:3.0.0-SNAPSHOT" container_name: fizz-manager-professional restart: always hostname: fizz-manager-professional diff --git a/fizz-bootstrap/pom.xml b/fizz-bootstrap/pom.xml index 7e46f76..13ebd0e 100644 --- a/fizz-bootstrap/pom.xml +++ b/fizz-bootstrap/pom.xml @@ -6,7 +6,7 @@ fizz-gateway-community com.fizzgate - 2.7.2 + 3.0.0-SNAPSHOT ../pom.xml diff --git a/fizz-common/pom.xml b/fizz-common/pom.xml index b109203..4ac6205 100644 --- a/fizz-common/pom.xml +++ b/fizz-common/pom.xml @@ -5,7 +5,7 @@ fizz-gateway-community com.fizzgate - 2.7.2 + 3.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index d35ef94..8d3960a 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -5,7 +5,7 @@ fizz-gateway-community com.fizzgate - 2.7.2 + 3.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/fizz-plugin/pom.xml b/fizz-plugin/pom.xml index c07b832..ce1b3ad 100644 --- a/fizz-plugin/pom.xml +++ b/fizz-plugin/pom.xml @@ -5,7 +5,7 @@ fizz-gateway-community com.fizzgate - 2.7.2 + 3.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/fizz-spring-boot-starter/pom.xml b/fizz-spring-boot-starter/pom.xml index a73333b..9be4d4b 100644 --- a/fizz-spring-boot-starter/pom.xml +++ b/fizz-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ fizz-gateway-community com.fizzgate - 2.7.2 + 3.0.0-SNAPSHOT ../pom.xml 4.0.0 diff --git a/pom.xml b/pom.xml index c1a0cd4..a7a8cee 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ fizz-gateway-community ${project.artifactId} fizz gateway community - 2.7.2 + 3.0.0-SNAPSHOT pom fizz-common From e674b35dfbb4c560172f2295b29f388172f6cf56 Mon Sep 17 00:00:00 2001 From: "nil.zhong" Date: Tue, 30 May 2023 16:59:01 +0800 Subject: [PATCH 02/14] Remove aggregate code, use new aggregate version by fizz-aggregate-spring-boot-starter. --- fizz-core/pom.xml | 7 + .../fizzgate/config/AppConfigProperties.java | 33 - .../config/RefreshLocalCacheConfig.java | 2 +- .../fizzgate/controller/ConfigController.java | 2 +- .../controller/ManagerConfigController.java | 2 +- .../fizzgate/controller/resp/ConfigResp.java | 4 +- .../exception/ExecuteScriptException.java | 67 -- .../fizzgate/exception/RedirectException.java | 45 - .../exception/StopAndResponseException.java | 45 - .../com/fizzgate/filter/AggregateFilter.java | 235 ----- .../filter/AggregateFilterProperties.java | 42 - .../filter/FilterExceptionHandlerConfig.java | 64 +- .../com/fizzgate/fizz/AggregateResource.java | 53 -- .../com/fizzgate/fizz/AggregateResult.java | 69 -- .../com/fizzgate/fizz/AggregateService.java | 134 --- .../java/com/fizzgate/fizz/ConfigLoader.java | 520 ---------- .../fizzgate/fizz/ConfigLoaderProperties.java | 36 - .../main/java/com/fizzgate/fizz/Pipeline.java | 585 ------------ .../src/main/java/com/fizzgate/fizz/Step.java | 207 ---- .../java/com/fizzgate/fizz/StepContext.java | 892 ------------------ .../java/com/fizzgate/fizz/StepResponse.java | 108 --- .../fizz/component/ComponentExecutor.java | 119 --- .../fizz/component/ComponentResult.java | 9 - .../fizz/component/ComponentTypeEnum.java | 50 - .../fizzgate/fizz/component/IComponent.java | 34 - .../fizzgate/fizz/component/OperatorEnum.java | 38 - .../fizz/component/StepContextPosition.java | 47 - .../fizz/component/circle/Circle.java | 337 ------- .../fizz/component/circle/CircleItem.java | 37 - .../fizz/component/condition/Condition.java | 321 ------- .../component/condition/ConditionValue.java | 59 -- .../fizz/exception/FizzRuntimeException.java | 54 -- .../com/fizzgate/fizz/field/FieldConfig.java | 59 -- .../fizz/field/FixedDataTypeEnum.java | 53 -- .../fizzgate/fizz/field/RefDataTypeEnum.java | 53 -- .../fizzgate/fizz/field/ValueTypeEnum.java | 49 - .../com/fizzgate/fizz/function/CodecFunc.java | 216 ----- .../fizzgate/fizz/function/CommonFunc.java | 177 ---- .../com/fizzgate/fizz/function/DateFunc.java | 300 ------ .../fizzgate/fizz/function/FuncExecutor.java | 535 ----------- .../com/fizzgate/fizz/function/IFunc.java | 39 - .../com/fizzgate/fizz/function/ListFunc.java | 231 ----- .../com/fizzgate/fizz/function/MathFunc.java | 225 ----- .../fizzgate/fizz/function/StringFunc.java | 307 ------ .../fizz/input/ClientInputConfig.java | 187 ---- .../java/com/fizzgate/fizz/input/IInput.java | 49 - .../java/com/fizzgate/fizz/input/Input.java | 113 --- .../com/fizzgate/fizz/input/InputConfig.java | 89 -- .../com/fizzgate/fizz/input/InputContext.java | 68 -- .../com/fizzgate/fizz/input/InputFactory.java | 107 --- .../com/fizzgate/fizz/input/InputType.java | 45 - .../com/fizzgate/fizz/input/PathMapping.java | 596 ------------ .../com/fizzgate/fizz/input/RPCInput.java | 156 --- .../com/fizzgate/fizz/input/RPCResponse.java | 48 - .../com/fizzgate/fizz/input/ScriptHelper.java | 163 ---- .../input/extension/dubbo/DubboInput.java | 247 ----- .../extension/dubbo/DubboInputConfig.java | 163 ---- .../extension/dubbo/DubboRPCResponse.java | 28 - .../input/extension/grpc/GRPCResponse.java | 38 - .../fizz/input/extension/grpc/GrpcInput.java | 246 ----- .../input/extension/grpc/GrpcInputConfig.java | 123 --- .../input/extension/request/RequestInput.java | 571 ----------- .../extension/request/RequestInputConfig.java | 225 ----- .../extension/request/RequestRPCResponse.java | 38 - .../GlobalResourceService.java | 11 +- .../listener/AggregateChannelListener.java | 211 ----- .../com/fizzgate/proxy/CallbackService.java | 10 +- .../fizzgate/proxy/FailAggregateResult.java | 5 +- .../com/fizzgate/proxy/FizzWebClient.java | 3 +- .../com/fizzgate/fizz/InputValidateTests.java | 137 --- .../fizzgate/fizz/component/CircleTests.java | 173 ---- .../fizz/component/ConditionTests.java | 331 ------- .../fizz/function/CodecFuncTests.java | 133 --- .../fizz/function/CommonFuncTests.java | 521 ---------- .../fizzgate/fizz/function/DateFuncTests.java | 92 -- .../fizz/function/FuncExecutorTests.java | 74 -- .../fizzgate/fizz/function/ListFuncTests.java | 324 ------- .../fizzgate/fizz/function/MathFuncTests.java | 217 ----- .../fizz/function/StringFuncTests.java | 224 ----- .../com/fizzgate/fizz/group/DevTestGroup.java | 4 - .../fizzgate/fizz/group/FastTestGroup.java | 4 - .../fizzgate/fizz/group/SlowTestGroup.java | 4 - .../fizz/input/DubboInputMockTests.java | 90 -- .../fizzgate/fizz/input/DubboInputTests.java | 45 - .../fizz/input/GrpcInputMockTests.java | 122 --- .../fizzgate/fizz/input/PathMappingTests.java | 226 ----- .../fizz/input/RequestInputTests.java | 87 -- .../ApacheDubboGenericServiceMockTests.java | 59 -- .../dubbo/ApacheDubboGenericServiceTests.java | 56 -- .../fizzgate/proxy/CallbackServiceTests.java | 2 +- .../grayrelease/GrayReleasePluginTests.java | 3 - .../main/resources/META-INF/spring.factories | 6 - 92 files changed, 39 insertions(+), 12936 deletions(-) delete mode 100644 fizz-core/src/main/java/com/fizzgate/config/AppConfigProperties.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/exception/ExecuteScriptException.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/exception/RedirectException.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/exception/StopAndResponseException.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/filter/AggregateFilter.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/filter/AggregateFilterProperties.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/AggregateResource.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/AggregateResult.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/AggregateService.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoader.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoaderProperties.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/Pipeline.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/Step.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/StepContext.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/StepResponse.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentExecutor.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentResult.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentTypeEnum.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/IComponent.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/OperatorEnum.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/StepContextPosition.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/circle/Circle.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/circle/CircleItem.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/condition/Condition.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/component/condition/ConditionValue.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/exception/FizzRuntimeException.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/field/FieldConfig.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/field/FixedDataTypeEnum.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/field/RefDataTypeEnum.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/field/ValueTypeEnum.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/CodecFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/CommonFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/DateFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/FuncExecutor.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/IFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/ListFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/MathFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/function/StringFunc.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/ClientInputConfig.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/IInput.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/Input.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/InputConfig.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/InputContext.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/InputFactory.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/InputType.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/PathMapping.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/RPCInput.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/RPCResponse.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/ScriptHelper.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInput.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInputConfig.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboRPCResponse.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GRPCResponse.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInput.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInputConfig.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInput.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInputConfig.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestRPCResponse.java delete mode 100644 fizz-core/src/main/java/com/fizzgate/listener/AggregateChannelListener.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/InputValidateTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/component/CircleTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/component/ConditionTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/CodecFuncTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/CommonFuncTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/DateFuncTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/FuncExecutorTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/ListFuncTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/MathFuncTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/function/StringFuncTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/group/DevTestGroup.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/group/FastTestGroup.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/group/SlowTestGroup.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputMockTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/GrpcInputMockTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/PathMappingTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/RequestInputTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceMockTests.java delete mode 100644 fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceTests.java diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index 8d3960a..53935a1 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -13,9 +13,16 @@ fizz-core + 1.0.0-SNAPSHOT + + com.fizzgate + fizz-aggregate-spring-boot-starter + ${aggregate.version} + + com.lmax disruptor diff --git a/fizz-core/src/main/java/com/fizzgate/config/AppConfigProperties.java b/fizz-core/src/main/java/com/fizzgate/config/AppConfigProperties.java deleted file mode 100644 index 6e4fa5a..0000000 --- a/fizz-core/src/main/java/com/fizzgate/config/AppConfigProperties.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.config; - -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.stereotype.Component; - -@RefreshScope -@Component -@Data -public class AppConfigProperties { - - @Value("${spring.profiles.active}") - private String env; -} - diff --git a/fizz-core/src/main/java/com/fizzgate/config/RefreshLocalCacheConfig.java b/fizz-core/src/main/java/com/fizzgate/config/RefreshLocalCacheConfig.java index 3148837..355a7b1 100644 --- a/fizz-core/src/main/java/com/fizzgate/config/RefreshLocalCacheConfig.java +++ b/fizz-core/src/main/java/com/fizzgate/config/RefreshLocalCacheConfig.java @@ -16,12 +16,12 @@ */ package com.fizzgate.config; +import com.fizzgate.aggregate.web.loader.ConfigLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; -import com.fizzgate.fizz.ConfigLoader; import com.fizzgate.plugin.auth.ApiConfig2appsService; import com.fizzgate.plugin.auth.ApiConfigService; import com.fizzgate.plugin.auth.AppService; diff --git a/fizz-core/src/main/java/com/fizzgate/controller/ConfigController.java b/fizz-core/src/main/java/com/fizzgate/controller/ConfigController.java index 807e2e1..e97539c 100644 --- a/fizz-core/src/main/java/com/fizzgate/controller/ConfigController.java +++ b/fizz-core/src/main/java/com/fizzgate/controller/ConfigController.java @@ -17,12 +17,12 @@ package com.fizzgate.controller; +import com.fizzgate.aggregate.web.loader.ConfigLoader; import org.apache.commons.io.FileUtils; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ServerWebExchange; -import com.fizzgate.fizz.ConfigLoader; import com.fizzgate.util.ScriptUtils; import reactor.core.publisher.Mono; diff --git a/fizz-core/src/main/java/com/fizzgate/controller/ManagerConfigController.java b/fizz-core/src/main/java/com/fizzgate/controller/ManagerConfigController.java index 263dbff..92e683c 100644 --- a/fizz-core/src/main/java/com/fizzgate/controller/ManagerConfigController.java +++ b/fizz-core/src/main/java/com/fizzgate/controller/ManagerConfigController.java @@ -17,6 +17,7 @@ package com.fizzgate.controller; +import com.fizzgate.aggregate.web.loader.ConfigLoader; import com.fizzgate.controller.req.BaseManagerConfigReq; import com.fizzgate.controller.req.GetApiConfigDetailReq; import com.fizzgate.controller.req.GetApiConfigReq; @@ -27,7 +28,6 @@ import com.fizzgate.controller.resp.ConfigResp; import com.fizzgate.controller.resp.ConfigStrResp; import com.fizzgate.controller.resp.GetApiConfigDetailResp; import com.fizzgate.controller.resp.GetApiConfigResp; -import com.fizzgate.fizz.ConfigLoader; import com.fizzgate.plugin.PluginConfig; import com.fizzgate.plugin.auth.ApiConfig; import com.fizzgate.plugin.auth.ApiConfig2appsService; diff --git a/fizz-core/src/main/java/com/fizzgate/controller/resp/ConfigResp.java b/fizz-core/src/main/java/com/fizzgate/controller/resp/ConfigResp.java index c69ed7d..9e55f8c 100644 --- a/fizz-core/src/main/java/com/fizzgate/controller/resp/ConfigResp.java +++ b/fizz-core/src/main/java/com/fizzgate/controller/resp/ConfigResp.java @@ -17,12 +17,12 @@ package com.fizzgate.controller.resp; +import com.fizzgate.aggregate.web.loader.ConfigLoader; + import java.io.Serializable; import java.util.List; import java.util.Objects; -import com.fizzgate.fizz.ConfigLoader; - /** * 聚合配置响应实体类 * @author zhongjie diff --git a/fizz-core/src/main/java/com/fizzgate/exception/ExecuteScriptException.java b/fizz-core/src/main/java/com/fizzgate/exception/ExecuteScriptException.java deleted file mode 100644 index ffd88f8..0000000 --- a/fizz-core/src/main/java/com/fizzgate/exception/ExecuteScriptException.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.exception; - -import com.fizzgate.fizz.StepContext; - -/** - * @author Francis - */ -public class ExecuteScriptException extends RuntimeException { - - private StepContext stepContext; - - private Object data; - - public ExecuteScriptException(String message, StepContext stepContext, Object data) { - super(message); - this.data = data; - this.stepContext = stepContext; - this.stepContext.setExceptionInfo(this, data); - } - - public ExecuteScriptException(Throwable cause, StepContext stepContext, Object data) { - super("execute script failed: " + cause.getMessage(), cause); - this.data = data; - this.stepContext = stepContext; - this.setStackTrace(cause.getStackTrace()); - this.stepContext.setExceptionInfo(this, data); - } - - /** - * - */ - private static final long serialVersionUID = 1L; - - public Object getData() { - return data; - } - - public void setData(Object data) { - this.data = data; - } - - public StepContext getStepContext() { - return stepContext; - } - - public void setStepContext(StepContext stepContext) { - this.stepContext = stepContext; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/exception/RedirectException.java b/fizz-core/src/main/java/com/fizzgate/exception/RedirectException.java deleted file mode 100644 index 27a2f65..0000000 --- a/fizz-core/src/main/java/com/fizzgate/exception/RedirectException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.exception; - -/** - * @author Francis - */ -public class RedirectException extends RuntimeException { - - private String redirectUrl; - - public RedirectException(String message, String redirectUrl) { - super(message); - this.redirectUrl = redirectUrl; - } - - /** - * - */ - private static final long serialVersionUID = 1L; - - public String getRedirectUrl() { - return redirectUrl; - } - - public void setRedirectUrl(String redirectUrl) { - this.redirectUrl = redirectUrl; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/exception/StopAndResponseException.java b/fizz-core/src/main/java/com/fizzgate/exception/StopAndResponseException.java deleted file mode 100644 index 2ff078f..0000000 --- a/fizz-core/src/main/java/com/fizzgate/exception/StopAndResponseException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.exception; - -/** - * @author Francis - */ -public class StopAndResponseException extends RuntimeException { - - private String data; - - public StopAndResponseException(String message, String data) { - super(message); - this.data = data; - } - - /** - * - */ - private static final long serialVersionUID = 1L; - - public String getData() { - return data; - } - - public void setData(String data) { - this.data = data; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/filter/AggregateFilter.java b/fizz-core/src/main/java/com/fizzgate/filter/AggregateFilter.java deleted file mode 100644 index 96a73fc..0000000 --- a/fizz-core/src/main/java/com/fizzgate/filter/AggregateFilter.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.filter; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.fizzgate.config.SystemConfig; -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.fizz.AggregateResource; -import com.fizzgate.fizz.AggregateResult; -import com.fizzgate.fizz.ConfigLoader; -import com.fizzgate.fizz.Pipeline; -import com.fizzgate.fizz.input.Input; -import com.fizzgate.plugin.auth.ApiConfig; -import com.fizzgate.util.Consts; -import com.fizzgate.util.MapUtil; -import com.fizzgate.util.NettyDataBufferUtils; -import com.fizzgate.util.WebUtils; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.ThreadContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.annotation.Order; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.codec.multipart.FilePart; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import javax.annotation.Resource; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -/** - * @author Francis Dong - */ -@Component -@Order(30) -public class AggregateFilter implements WebFilter { - - private static final Logger LOGGER = LoggerFactory.getLogger(AggregateFilter.class); - - private static final String X_FORWARDED_FOR = "X-FORWARDED-FOR"; - - @Resource - private ConfigLoader configLoader; - - @Resource - private AggregateFilterProperties aggregateFilterProperties; - - @Resource - private SystemConfig systemConfig; - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - - String serviceId = WebUtils.getBackendService(exchange); - if (serviceId == null) { - return chain.filter(exchange); - } else if (WebUtils.ignorePlugin(exchange) && WebUtils.getRoute(exchange).type == ApiConfig.Type.SERVICE_AGGREGATE) { - } else { - byte act = WebUtils.getApiConfigType(exchange); - if (act == ApiConfig.Type.UNDEFINED) { - String p = exchange.getRequest().getURI().getPath(); - if (StringUtils.startsWith(p, SystemConfig.DEFAULT_GATEWAY_TEST_PREFIX0)) { - if (systemConfig.isAggregateTestAuth()) { - return chain.filter(exchange); - } - } else if (aggregateFilterProperties.isNeedAuth()) { - return chain.filter(exchange); - } - } else if (act != ApiConfig.Type.SERVICE_AGGREGATE) { - return chain.filter(exchange); - } - } - - FilterResult pfr = WebUtils.getPrevFilterResult(exchange); - if (!pfr.success) { - return WebUtils.getDirectResponse(exchange); - } - - long start = System.currentTimeMillis(); - ServerHttpRequest request = exchange.getRequest(); - ServerHttpResponse serverHttpResponse = exchange.getResponse(); - - String clientReqPathPrefix = WebUtils.getClientReqPathPrefix(exchange); - String path = clientReqPathPrefix + serviceId + WebUtils.getBackendPath(exchange); - String method = request.getMethodValue(); - if (HttpMethod.HEAD.matches(method.toUpperCase())) { - method = HttpMethod.GET.name(); - } - AggregateResource aggregateResource = configLoader.matchAggregateResource(method, path); - if (aggregateResource == null) { - if (SystemConfig.DEFAULT_GATEWAY_TEST_PREFIX0.equals(clientReqPathPrefix) || - WebUtils.getApiConfigType(exchange) == ApiConfig.Type.SERVICE_AGGREGATE) { - return WebUtils.responseError(exchange, HttpStatus.NOT_FOUND.value(), "API not found in aggregation: " + path); - } else { - return chain.filter(exchange); - } - } - - Pipeline pipeline = aggregateResource.getPipeline(); - Input input = aggregateResource.getInput(); - - HttpHeaders hds = request.getHeaders(); - Map headers = MapUtil.headerToHashMap(hds); - if (CollectionUtils.isEmpty(hds.get(X_FORWARDED_FOR)) && systemConfig.isFizzWebClientXForwardedForEnable()) { - headers.put(X_FORWARDED_FOR, WebUtils.getOriginIp(exchange)); - } - Map fizzHeaders = (Map) exchange.getAttributes().get(WebUtils.APPEND_HEADERS); - if (fizzHeaders != null && !fizzHeaders.isEmpty()) { - Set> entrys = fizzHeaders.entrySet(); - for (Entry entry : entrys) { - headers.put(entry.getKey().toUpperCase(), entry.getValue()); - } - } - - // traceId - final String traceId = WebUtils.getTraceId(exchange); - // LogService.setBizId(traceId); - ThreadContext.put(Consts.TRACE_ID, traceId); - - LOGGER.debug("{} matched api in aggregation: {}", traceId, path); - - // 客户端提交上来的信息 - Map clientInput = new HashMap<>(); - clientInput.put("path", path); - clientInput.put("method", method); - clientInput.put("headers", headers); - clientInput.put("params", MapUtil.toHashMap(request.getQueryParams())); - clientInput.put("contentType", request.getHeaders().getFirst(CommonConstants.HEADER_CONTENT_TYPE)); - - Mono result = null; - MediaType contentType = request.getHeaders().getContentType(); - - if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) { - result = exchange.getMultipartData().flatMap(md -> { - Map filePartMap = new HashMap<>(); - clientInput.put("body", MapUtil.extractFormData(md, CommonConstants.FILE_KEY_PREFIX, filePartMap)); - clientInput.put("filePartMap", filePartMap); - return pipeline.run(input, clientInput, traceId); - }); - } else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) { - result = exchange.getFormData().flatMap(fd -> { - clientInput.put("body", MapUtil.toHashMap(fd)); - return pipeline.run(input, clientInput, traceId); - }); - } else { - if (HttpMethod.POST.name().equalsIgnoreCase(method)) { - result = DataBufferUtils.join(request.getBody()).defaultIfEmpty(NettyDataBufferUtils.EMPTY_DATA_BUFFER).flatMap(buf -> { - if (buf != NettyDataBufferUtils.EMPTY_DATA_BUFFER) { - try { - clientInput.put("body", buf.toString(StandardCharsets.UTF_8)); - } finally { - DataBufferUtils.release(buf); - } - } - return pipeline.run(input, clientInput, traceId); - }); - } else { - result = pipeline.run(input, clientInput, traceId); - } - } - return result.subscribeOn(Schedulers.elastic()).flatMap(aggResult -> { - // LogService.setBizId(traceId); - ThreadContext.put(Consts.TRACE_ID, traceId); - if (aggResult.getHttpStatus() != null) { - serverHttpResponse.setRawStatusCode(aggResult.getHttpStatus()); - } - String jsonString = null; - if (aggResult.getBody() instanceof String) { - jsonString = (String) aggResult.getBody(); - } else { - if (this.aggregateFilterProperties.isWriteMapNullValue()) { - jsonString = JSON.toJSONString(aggResult.getBody(), SerializerFeature.WriteMapNullValue); - } else { - jsonString = JSON.toJSONString(aggResult.getBody()); - } - } - LOGGER.debug("{} response body: {}", traceId, jsonString); - if (aggResult.getHeaders() != null && !aggResult.getHeaders().isEmpty()) { - serverHttpResponse.getHeaders().addAll(aggResult.getHeaders()); - serverHttpResponse.getHeaders().remove(CommonConstants.HEADER_CONTENT_LENGTH); - } - if (!serverHttpResponse.getHeaders().containsKey(CommonConstants.HEADER_CONTENT_TYPE)) { - // default content-type - serverHttpResponse.getHeaders().add(CommonConstants.HEADER_CONTENT_TYPE, CommonConstants.CONTENT_TYPE_JSON); - } - List headerTraceIds = serverHttpResponse.getHeaders().get(systemConfig.fizzTraceIdHeader()); - if (headerTraceIds == null || !headerTraceIds.contains(traceId)) { - serverHttpResponse.getHeaders().add(systemConfig.fizzTraceIdHeader(), traceId); - } - - long end = System.currentTimeMillis(); - pipeline.getStepContext().addElapsedTime("总耗时", end - start); - LOGGER.info("{} ElapsedTimes={}", traceId, JSON.toJSONString(pipeline.getStepContext().getElapsedTimes())); - - return serverHttpResponse - .writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(jsonString.getBytes()))); - }); - - } - -} \ No newline at end of file diff --git a/fizz-core/src/main/java/com/fizzgate/filter/AggregateFilterProperties.java b/fizz-core/src/main/java/com/fizzgate/filter/AggregateFilterProperties.java deleted file mode 100644 index 1063af5..0000000 --- a/fizz-core/src/main/java/com/fizzgate/filter/AggregateFilterProperties.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.filter; - -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.stereotype.Component; - -import com.fizzgate.context.config.annotation.FizzRefreshScope; - -/** - * {@link AggregateFilter} properties - * - * @author zhongjie - */ -//@RefreshScope -@FizzRefreshScope -@Component -@Data -public class AggregateFilterProperties { - - @Value("${need-auth:true}") - private boolean needAuth; - - @Value("${fizz.aggregate.writeMapNullValue:false}") - private boolean writeMapNullValue; -} diff --git a/fizz-core/src/main/java/com/fizzgate/filter/FilterExceptionHandlerConfig.java b/fizz-core/src/main/java/com/fizzgate/filter/FilterExceptionHandlerConfig.java index e773488..83d1e90 100644 --- a/fizz-core/src/main/java/com/fizzgate/filter/FilterExceptionHandlerConfig.java +++ b/fizz-core/src/main/java/com/fizzgate/filter/FilterExceptionHandlerConfig.java @@ -17,6 +17,12 @@ package com.fizzgate.filter; +import com.fizzgate.Fizz; +import com.fizzgate.aggregate.web.util.AggregateExceptionHandleUtils; +import com.fizzgate.config.SystemConfig; +import com.fizzgate.util.Consts; +import com.fizzgate.util.ThreadContext; +import com.fizzgate.util.WebUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,23 +36,8 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; - -import com.fizzgate.Fizz; -import com.fizzgate.config.SystemConfig; -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.exception.RedirectException; -import com.fizzgate.exception.StopAndResponseException; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.legacy.RespEntity; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; -import com.fizzgate.util.ThreadContext; -import com.fizzgate.util.WebUtils; - import reactor.core.publisher.Mono; -import java.net.URI; - /** * @author hongqiaowei */ @@ -84,53 +75,14 @@ public class FilterExceptionHandlerConfig { respHeaders.set(WebUtils.BODY_ENCRYPT, Consts.S.FALSE0); } - if (t instanceof StopAndResponseException) { - StopAndResponseException ex = (StopAndResponseException) t; - if (ex.getData() != null) { - respHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(ex.getData().toString().getBytes()))); - } - } - if (t instanceof RedirectException) { - RedirectException ex = (RedirectException) t; - if (ex.getRedirectUrl() != null) { - resp.setStatusCode(HttpStatus.MOVED_PERMANENTLY); - respHeaders.setLocation(URI.create(ex.getRedirectUrl())); - return Mono.empty(); - } + if (AggregateExceptionHandleUtils.needHandle(t)) { + return AggregateExceptionHandleUtils.handle(exchange, respHeaders, resp, t, traceId, LOGGER); } String tMsg = t.getMessage(); if (tMsg == null) { tMsg = t.toString(); } - if (t instanceof ExecuteScriptException) { - ExecuteScriptException ex = (ExecuteScriptException) t; - respHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - RespEntity rs = null; - if (ex.getStepContext() != null && ex.getStepContext().returnContext()) { - rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), tMsg, traceId, ex.getStepContext()); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsBytes(rs)))); - } else { - rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), tMsg, traceId); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); - } - } - - if (t instanceof FizzRuntimeException) { - FizzRuntimeException ex = (FizzRuntimeException) t; - org.apache.logging.log4j.ThreadContext.put(Consts.TRACE_ID, traceId); - LOGGER.error(tMsg, ex); - respHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - RespEntity rs = null; - if (ex.getStepContext() != null && ex.getStepContext().returnContext()) { - rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), tMsg, traceId, ex.getStepContext()); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsString(rs).getBytes()))); - } else { - rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), tMsg, traceId); - return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); - } - } Mono vm; Object fc = exchange.getAttribute(WebUtils.FILTER_CONTEXT); diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/AggregateResource.java b/fizz-core/src/main/java/com/fizzgate/fizz/AggregateResource.java deleted file mode 100644 index bd5f4a4..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/AggregateResource.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz; - -import com.fizzgate.fizz.input.Input; - -/** - * - * @author Francis Dong - * - */ -public class AggregateResource { - - private Pipeline pipeline; - private Input input; - - public AggregateResource(Pipeline pipeline, Input input) { - super(); - this.pipeline = pipeline; - this.input = input; - } - - public Pipeline getPipeline() { - return pipeline; - } - - public void setPipeline(Pipeline pipeline) { - this.pipeline = pipeline; - } - - public Input getInput() { - return input; - } - - public void setInput(Input input) { - this.input = input; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/AggregateResult.java b/fizz-core/src/main/java/com/fizzgate/fizz/AggregateResult.java deleted file mode 100644 index 199735a..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/AggregateResult.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import org.springframework.util.MultiValueMap; - -/** - * - * @author Francis Dong - * - */ -public class AggregateResult { - - private Integer httpStatus; - - private MultiValueMap headers; - - private Object body; - - private StepContext stepContext; - - public MultiValueMap getHeaders() { - return headers; - } - - public void setHeaders(MultiValueMap headers) { - this.headers = headers; - } - - public Object getBody() { - return body; - } - - public void setBody(Object body) { - this.body = body; - } - - public StepContext getStepContext() { - return stepContext; - } - - public void setStepContext(StepContext stepContext) { - this.stepContext = stepContext; - } - - public Integer getHttpStatus() { - return httpStatus; - } - - public void setHttpStatus(Integer httpStatus) { - this.httpStatus = httpStatus; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/AggregateService.java b/fizz-core/src/main/java/com/fizzgate/fizz/AggregateService.java deleted file mode 100644 index 763e88c..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/AggregateService.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.config.SystemConfig; -import com.fizzgate.fizz.input.Input; -import com.fizzgate.util.Consts; -import com.fizzgate.util.MapUtil; -import com.fizzgate.util.Utils; -import com.fizzgate.util.WebUtils; - -import org.apache.logging.log4j.ThreadContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.stereotype.Service; -import org.springframework.util.MultiValueMap; -import org.springframework.web.server.ServerWebExchange; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import javax.annotation.Resource; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author hongqiaowei - */ - -@Service -public class AggregateService { - - private static final Logger log = LoggerFactory.getLogger(AggregateService.class); - - @Resource - private ConfigLoader aggregateResourceLoader; - - @Resource - private SystemConfig systemConfig; - - public Mono request(String traceId, String clientReqPathPrefix, String method, String service, String path, MultiValueMap queryParams, - HttpHeaders headers, String body) { - - // long start = System.currentTimeMillis(); - // ServerHttpRequest request = exchange.getRequest(); - String pash = clientReqPathPrefix + service + path; - // String method = request.getMethodValue(); - AggregateResource aggregateResource = aggregateResourceLoader.matchAggregateResource(method, pash); - if (aggregateResource == null) { - return Mono.error(Utils.runtimeExceptionWithoutStack("no aggregate resource: " + method + ' ' + pash)); - } else { - Pipeline pipeline = aggregateResource.getPipeline(); - Input input = aggregateResource.getInput(); - Map hs = MapUtil.toHashMap(headers); - // LogService.setBizId(traceId); - ThreadContext.put(Consts.TRACE_ID, traceId); - log.debug("matched aggregation api: {}", pash); - Map clientInput = new HashMap<>(); - clientInput.put("path", pash); - clientInput.put("method", method); - clientInput.put("headers", hs); - // MultiValueMap queryParams = request.getQueryParams(); - if (queryParams != null) { - clientInput.put("params", MapUtil.toHashMap(queryParams)); - } - if (body != null) { - clientInput.put("body", JSON.parse(body)); - } - return pipeline.run(input, clientInput, traceId).subscribeOn(Schedulers.elastic()); - } - } - - public Mono request(String traceId, String clientReqPathPrefix, String method, String service, String path, MultiValueMap queryParams, - HttpHeaders headers, DataBuffer body) { - String b = null; - if (body != null) { - b = body.toString(StandardCharsets.UTF_8); - } - return request(traceId, clientReqPathPrefix, method, service, path, queryParams, headers, b); - } - - public Mono genAggregateResponse(ServerWebExchange exchange, AggregateResult ar) { - ServerHttpResponse clientResp = exchange.getResponse(); - String traceId = WebUtils.getTraceId(exchange); - // LogService.setBizId(traceId); - ThreadContext.put(Consts.TRACE_ID, traceId); - String js = null; - if(ar.getBody() instanceof String) { - js = (String) ar.getBody(); - }else { - js = JSON.toJSONString(ar.getBody()); - } - log.debug("aggregate response body: {}", js); - if (ar.getHeaders() != null && !ar.getHeaders().isEmpty()) { - ar.getHeaders().remove("Content-Length"); - clientResp.getHeaders().addAll(ar.getHeaders()); - } - if (!clientResp.getHeaders().containsKey("Content-Type")) { - // defalut content-type - clientResp.getHeaders().add("Content-Type", "application/json; charset=UTF-8"); - } - List headerTraceIds = clientResp.getHeaders().get(systemConfig.fizzTraceIdHeader()); - if (headerTraceIds == null || !headerTraceIds.contains(traceId)) { - clientResp.getHeaders().add(systemConfig.fizzTraceIdHeader(), traceId); - } - // long end = System.currentTimeMillis(); - // pipeline.getStepContext().addElapsedTime("总耗时", end - start); - // log.info("ElapsedTimes={}", JSON.toJSONString(pipeline.getStepContext().getElapsedTimes())); - return clientResp - .writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(js.getBytes()))); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoader.java b/fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoader.java deleted file mode 100644 index 54565a1..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoader.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.fizzgate.config.AppConfigProperties; -import com.fizzgate.fizz.input.ClientInputConfig; -import com.fizzgate.fizz.input.Input; -import com.fizzgate.fizz.input.InputFactory; -import com.fizzgate.fizz.input.InputType; -import com.fizzgate.util.Consts; -import com.fizzgate.util.ReactorUtils; - -import org.apache.commons.io.FileUtils; -import org.apache.logging.log4j.ThreadContext; -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.data.redis.core.ReactiveStringRedisTemplate; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.lang.ref.SoftReference; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -import static com.fizzgate.config.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE; -import static com.fizzgate.util.Consts.S.FORWARD_SLASH; -import static com.fizzgate.util.Consts.S.FORWARD_SLASH_STR; - -/** - * - * @author Francis Dong - * @author zhongjie - * - */ -@Component -public class ConfigLoader { - /** - * legacy aggregate formal path prefix - */ - private static final String LEGACY_FORMAL_PATH_PREFIX = "/proxy"; - /** - * legacy aggregate test path prefix - */ - private static final String LEGACY_TEST_PATH_PREFIX = "/proxytest"; - /** - * aggregate test path prefix - */ - private static final String TEST_PATH_PREFIX = "/_proxytest"; - /** - * aggregate test path service name start index - */ - private static final int TEST_PATH_SERVICE_NAME_START_INDEX = TEST_PATH_PREFIX.length() + 1; - - @Autowired - public ConfigurableApplicationContext appContext; - private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLoader.class); - - /** - * 聚合配置存放Hash的Key - */ - private static final String AGGREGATE_HASH_KEY = "fizz_aggregate_config"; - - private static Map aggregateResources = null; - private static Map resourceKey2ConfigInfoMap = null; - private static Map aggregateId2ResourceKeyMap = null; - - @Resource - private AppConfigProperties appConfigProperties; - - @Resource(name = AGGREGATE_REACTIVE_REDIS_TEMPLATE) - private ReactiveStringRedisTemplate reactiveStringRedisTemplate; - - @Resource - private ConfigLoaderProperties configLoaderProperties; - - private String formalPathPrefix; - private int formalPathServiceNameStartIndex; - - public Input createInput(String configStr) throws IOException { - ONode cfgNode = ONode.loadStr(configStr); - - Input input = new Input(); - input.setName(cfgNode.select("$.name").getString()); - - ClientInputConfig clientInputConfig = new ClientInputConfig(); - clientInputConfig.setDataMapping(cfgNode.select("$.dataMapping").toObject(Map.class)); - clientInputConfig.setHeaders(cfgNode.select("$.headers").toObject(Map.class)); - clientInputConfig.setMethod(cfgNode.select("$.method").getString()); - clientInputConfig.setPath(cfgNode.select("$.path").getString()); - if (clientInputConfig.getPath().startsWith(TEST_PATH_PREFIX)) { - // always enable debug for testing - clientInputConfig.setDebug(true); - } else { - if (cfgNode.select("$.debug") != null) { - clientInputConfig.setDebug(cfgNode.select("$.debug").getBoolean()); - } - } - clientInputConfig.setType(InputType.valueOf(cfgNode.select("$.type").getString())); - clientInputConfig.setLangDef(cfgNode.select("$.langDef").toObject(Map.class)); - clientInputConfig.setBodyDef(cfgNode.select("$.bodyDef").toObject(Map.class)); - clientInputConfig.setHeadersDef(cfgNode.select("$.headersDef").toObject(Map.class)); - clientInputConfig.setParamsDef(cfgNode.select("$.paramsDef").toObject(Map.class)); - clientInputConfig.setScriptValidate(cfgNode.select("$.scriptValidate").toObject(Map.class)); - clientInputConfig.setValidateResponse(cfgNode.select("$.validateResponse").toObject(Map.class)); - clientInputConfig.setContentType(cfgNode.select("$.contentType").getString()); - clientInputConfig.setXmlArrPaths(cfgNode.select("$.xmlArrPaths").getString()); - input.setConfig(clientInputConfig); - return input; - } - - public Pipeline createPipeline(String configStr) throws IOException { - ONode cfgNode = ONode.loadStr(configStr); - - Pipeline pipeline = new Pipeline(); - pipeline.setApplicationContext(appContext); - - List> stepConfigs = cfgNode.select("$.stepConfigs").toObject(List.class); - for (Map stepConfig : stepConfigs) { - // set the specified env URL - this.handleRequestURL(stepConfig); - SoftReference weakPipeline = new SoftReference(pipeline); - Step step = new Step.Builder().read(stepConfig, weakPipeline); - step.setName((String) stepConfig.get("name")); - if (stepConfig.get("stop") != null) { - step.setStop((Boolean) stepConfig.get("stop")); - } else { - step.setStop(false); - } - step.setDataMapping((Map) stepConfig.get("dataMapping")); - pipeline.addStep(step); - } - - return pipeline; - } - - public List getConfigInfo() { - if (aggregateResources == null) { - try { - this.init(); - } catch (Exception e) { - e.printStackTrace(); - } - } - return new ArrayList<>(resourceKey2ConfigInfoMap.values()); - } - - public String getConfigStr(String configId) { - if (aggregateResources == null) { - try { - this.init(); - } catch (Exception e) { - e.printStackTrace(); - } - } - String resourceKey = aggregateId2ResourceKeyMap.get(configId); - if (resourceKey == null) { - return null; - } - return aggregateResources.get(resourceKey); - } - - private void handleRequestURL(Map stepConfig) { - List requests = (List) stepConfig.get("requests"); - for (Object obj : requests) { - Map request = (Map) obj; - String envUrl = (String) request.get(appConfigProperties.getEnv() + "Url"); - if (!StringUtils.isEmpty(envUrl)) { - request.put("url", request.get(appConfigProperties.getEnv() + "Url")); - } - } - } - - @PostConstruct - public synchronized void init() throws Exception { - this.refreshLocalCache(); - InputFactory.loadInputClasses(); - } - - - public synchronized void refreshLocalCache() throws Exception { - if (formalPathPrefix == null) { - String formalPathPrefixTmp = appContext.getEnvironment().getProperty("gateway.prefix", "/proxy"); - if (formalPathPrefixTmp.endsWith(FORWARD_SLASH_STR)) { - // remove the end slash - formalPathPrefixTmp = formalPathPrefixTmp.substring(0, formalPathPrefixTmp.length() - 1); - } - formalPathPrefix = formalPathPrefixTmp; - formalPathServiceNameStartIndex = formalPathPrefix.length() + 1; - } - - Map aggregateResourcesTmp = new ConcurrentHashMap<>(1024); - Map resourceKey2ConfigInfoMapTmp = new ConcurrentHashMap<>(1024); - Map aggregateId2ResourceKeyMapTmp = new ConcurrentHashMap<>(1024); - - if (configLoaderProperties.getReadLocalConfigFlag()) { - File dir = new File("json"); - if (dir.exists() && dir.isDirectory()) { - File[] files = dir.listFiles(); - if (files != null && files.length > 0) { - for (File file : files) { - if (!file.exists()) { - throw new IOException("File not found"); - } - String configStr = FileUtils.readFileToString(file, StandardCharsets.UTF_8); - this.addConfig(configStr, aggregateResourcesTmp, resourceKey2ConfigInfoMapTmp, aggregateId2ResourceKeyMapTmp); - } - } - } - } else { - // 从Redis缓存中获取配置 - final Throwable[] throwable = new Throwable[1]; - Throwable error = Mono.just(Objects.requireNonNull(reactiveStringRedisTemplate.opsForHash().entries(AGGREGATE_HASH_KEY) - .defaultIfEmpty(new AbstractMap.SimpleEntry<>(ReactorUtils.OBJ, ReactorUtils.OBJ)).onErrorStop().doOnError(t -> LOGGER.info(null, t)) - .concatMap(entry -> { - Object k = entry.getKey(); - if (k == ReactorUtils.OBJ) { - return Flux.just(entry); - } - String configStr = (String) entry.getValue(); - // LOGGER.info("aggregate config: " + k.toString() + Consts.S.COLON + configStr, LogService.BIZ_ID, k.toString()); - - ThreadContext.put(Consts.TRACE_ID, k.toString()); - LOGGER.info("aggregate config: " + k.toString() + Consts.S.COLON + configStr); - - try { - this.addConfig(configStr, aggregateResourcesTmp, resourceKey2ConfigInfoMapTmp, aggregateId2ResourceKeyMapTmp); - return Flux.just(entry); - } catch (Throwable t) { - throwable[0] = t; - LOGGER.info(configStr, t); - return Flux.error(t); - } - }).blockLast())).flatMap( - e -> { - if (throwable[0] != null) { - return Mono.error(throwable[0]); - } - return Mono.just(ReactorUtils.EMPTY_THROWABLE); - } - ).block(); - if (error != ReactorUtils.EMPTY_THROWABLE) { - assert error != null; - throw new RuntimeException(error); - } - } - - aggregateResources = aggregateResourcesTmp; - resourceKey2ConfigInfoMap = resourceKey2ConfigInfoMapTmp; - aggregateId2ResourceKeyMap = aggregateId2ResourceKeyMapTmp; - } - - public synchronized void addConfig(String configStr) { - if (aggregateResources == null) { - try { - this.init(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - this.addConfig(configStr, aggregateResources, resourceKey2ConfigInfoMap, aggregateId2ResourceKeyMap); - } - - private void addConfig(String configStr, Map aggregateResources, - Map resourceKey2ConfigInfoMap, Map aggregateId2ResourceKeyMap) { - ONode cfgNode = ONode.loadStr(configStr); - - boolean needReGenConfigStr = false; - // in the future aggregate config will add this field and remove the prefix '/proxy'|'/proxytest' of path - boolean existAggrVersion = cfgNode.contains("aggrVersion"); - - String method = cfgNode.select("$.method").getString(); - String path = cfgNode.select("$.path").getString(); - - if (!existAggrVersion) { - if (path.startsWith(LEGACY_TEST_PATH_PREFIX)) { - // legacy test path, remove prefix '/proxytest' - path = path.replaceFirst(LEGACY_TEST_PATH_PREFIX, TEST_PATH_PREFIX); - needReGenConfigStr = true; - } else if (path.startsWith(LEGACY_FORMAL_PATH_PREFIX)) { - // legacy formal path, remove prefix '/proxy' - path = path.replace(LEGACY_FORMAL_PATH_PREFIX, ""); - needReGenConfigStr = true; - } - } - - if (!path.startsWith(TEST_PATH_PREFIX)) { - // formal path add the custom gateway prefix - path = String.format("%s%s", formalPathPrefix, path); - needReGenConfigStr = true; - } - - String resourceKey = method.toUpperCase() + ":" + path; - String configId = cfgNode.select("$.id").getString(); - String configName = cfgNode.select("$.name").getString(); - long version = cfgNode.select("$.version").getLong(); - - if (needReGenConfigStr) { - cfgNode.set("path", path); - configStr = cfgNode.toJson(); - } - - LOGGER.debug("add aggregation config, key={} config={}", resourceKey, configStr); - if (StringUtils.hasText(configId)) { - String existResourceKey = aggregateId2ResourceKeyMap.get(configId); - if (StringUtils.hasText(existResourceKey)) { - // 删除旧有的配置 - aggregateResources.remove(existResourceKey); - resourceKey2ConfigInfoMap.remove(existResourceKey); - } - aggregateId2ResourceKeyMap.put(configId, resourceKey); - } - aggregateResources.put(resourceKey, configStr); - resourceKey2ConfigInfoMap.put(resourceKey, this.buildConfigInfo(configId, configName, method, path, version)); - } - - public synchronized void deleteConfig(String configIds) { - if (CollectionUtils.isEmpty(aggregateId2ResourceKeyMap)) { - return; - } - - JSONArray idArray = JSON.parseArray(configIds); - idArray.forEach(it -> { - String configId = (String) it; - String existResourceKey = aggregateId2ResourceKeyMap.get(configId); - if (StringUtils.hasText(existResourceKey)) { - LOGGER.debug("delete aggregation config: {}", existResourceKey); - aggregateResources.remove(existResourceKey); - resourceKey2ConfigInfoMap.remove(existResourceKey); - aggregateId2ResourceKeyMap.remove(configId); - } - }); - } - - public AggregateResource matchAggregateResource(String method, String path) { - if (aggregateResources == null) { - try { - init(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - String key = method.toUpperCase() + ":" + path; - // config file entry ,if you want modify the aggregate config json but not use the interface of fizz, - // you can just read the config ,transform to json format and modify it - if (aggregateResources.containsKey(key) && aggregateResources.get(key) != null) { - String configStr = aggregateResources.get(key); - Input input = null; - Pipeline pipeline = null; - try { - input = createInput(configStr); - pipeline = createPipeline(configStr); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - if (pipeline != null && input != null) { - ClientInputConfig cfg = (ClientInputConfig) input.getConfig(); - return new AggregateResource(pipeline, input); - } - } - return null; - } - - private ConfigInfo buildConfigInfo(String configId, String configName, String method, String path, long version) { - String serviceName = this.extractServiceName(path); - ConfigInfo configInfo = new ConfigInfo(); - configInfo.setConfigId(configId); - configInfo.setConfigName(configName); - configInfo.setServiceName(serviceName); - configInfo.setMethod(method); - configInfo.setPath(path); - configInfo.setVersion(version == 0 ? null : version); - return configInfo; - } - - private String extractServiceName(String path) { - if (path != null) { - if (path.startsWith(TEST_PATH_PREFIX)) { - int endIndex = path.indexOf(FORWARD_SLASH, TEST_PATH_SERVICE_NAME_START_INDEX); - if (endIndex > TEST_PATH_SERVICE_NAME_START_INDEX) { - return path.substring(TEST_PATH_SERVICE_NAME_START_INDEX, endIndex); - } - } else if (path.startsWith(formalPathPrefix)) { - int endIndex = path.indexOf(FORWARD_SLASH, formalPathServiceNameStartIndex); - if (endIndex > formalPathServiceNameStartIndex) { - return path.substring(formalPathServiceNameStartIndex, endIndex); - } - } - } - return null; - } - - public static class ConfigInfo implements Serializable { - private static final long serialVersionUID = 1L; - /** - * 配置ID - */ - private String configId; - - /** - * 配置名 - */ - private String configName; - - /** - * 服务名 - */ - private String serviceName; - /** - * 接口请求method类型 - */ - private String method; - /** - * 接口请求路径 - */ - private String path; - /** - * 版本号 - */ - private Long version; - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ConfigInfo that = (ConfigInfo) o; - return Objects.equals(configId, that.configId) && Objects.equals(configName, that.configName) - && Objects.equals(serviceName, that.serviceName) && Objects.equals(method, that.method) - && Objects.equals(path, that.path) && Objects.equals(version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(configId, configName, serviceName, method, path, version); - } - - public String getConfigId() { - return configId; - } - - public void setConfigId(String configId) { - this.configId = configId; - } - - public String getConfigName() { - return configName; - } - - public void setConfigName(String configName) { - this.configName = configName; - } - - public String getServiceName() { - return serviceName; - } - - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - - public String getMethod() { - return method; - } - - public void setMethod(String method) { - this.method = method; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public Long getVersion() { - return version; - } - - public void setVersion(Long version) { - this.version = version; - } - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoaderProperties.java b/fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoaderProperties.java deleted file mode 100644 index 3e6cd7f..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/ConfigLoaderProperties.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz; - -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.stereotype.Component; - -/** - * {@link ConfigLoader} properties - * - * @author zhongjie - */ -@RefreshScope -@Component -@Data -public class ConfigLoaderProperties { - - @Value("${fizz.aggregate.read-local-config-flag:false}") - private Boolean readLocalConfigFlag; -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/Pipeline.java b/fizz-core/src/main/java/com/fizzgate/fizz/Pipeline.java deleted file mode 100644 index db5e4c9..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/Pipeline.java +++ /dev/null @@ -1,585 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.exception.RedirectException; -import com.fizzgate.exception.StopAndResponseException; -import com.fizzgate.fizz.component.ComponentExecutor; -import com.fizzgate.fizz.component.ComponentResult; -import com.fizzgate.fizz.component.IComponent; -import com.fizzgate.fizz.component.StepContextPosition; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.field.FieldConfig; -import com.fizzgate.fizz.field.ValueTypeEnum; -import com.fizzgate.fizz.input.*; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; -import com.fizzgate.util.JsonSchemaUtils; -import com.fizzgate.util.MapUtil; -import com.fizzgate.xml.JsonToXml; -import com.fizzgate.xml.XmlToJson; -import com.fizzgate.xml.XmlToJson.Builder; - -import org.apache.logging.log4j.ThreadContext; -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.data.util.Pair; -import org.springframework.http.HttpHeaders; -import org.springframework.http.codec.multipart.FilePart; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import com.fizzgate.schema.util.I18nUtils; -import com.fizzgate.schema.util.PropertiesSupportUtils; - -import javax.script.ScriptException; -import java.util.*; - -/** - * - * @author linwaiwai - * @author Francis Dong - * @author zhongjie - * - */ -public class Pipeline { - private static final String CONTENT_TYPE_XML = "application/xml"; - private ConfigurableApplicationContext applicationContext; - private static final Logger LOGGER = LoggerFactory.getLogger(Pipeline.class); - private LinkedList steps = new LinkedList(); - private StepContext stepContext = new StepContext<>(); - public void addStep(Step step) { - steps.add(step); - } - - static void displayValue(String n) { - System.out.println("input : " + n); - } - - public StepContext getStepContext(){ - return this.stepContext; - } - - public Mono run(Input input, Map clientInput, String traceId) { - return this.runPipeline(input, clientInput, traceId).onErrorResume((ex) -> { - String message = ex.getMessage(); - if (ex.getMessage() == null) { - message = "failed to run aggregation pipeline, message: " + ex.toString(); - StackTraceElement[] stacks = ex.getStackTrace(); - if (stacks != null && stacks.length > 0) { - message = message + " at " + stacks[0]; - } - } - if (ex instanceof StopAndResponseException) { - throw (StopAndResponseException) ex; - } - if (ex instanceof RedirectException) { - throw (RedirectException) ex; - } - if (ex instanceof ExecuteScriptException) { - throw (ExecuteScriptException) ex; - } - if (ex instanceof FizzRuntimeException && ex.getMessage() != null) { - FizzRuntimeException e = (FizzRuntimeException) ex; - if (e.getStepContext() == null) { - e.setStepContext(stepContext); - } - throw e; - } else { - throw new FizzRuntimeException(message, ex, stepContext); - } - }); - } - - public Mono runPipeline(Input input, Map clientInput, String traceId) { - ClientInputConfig config = (ClientInputConfig)input.getConfig(); - this.initialStepContext(clientInput, config); - this.stepContext.setDebug(config.isDebug()); - this.stepContext.setApplicationContext(applicationContext); - - if(traceId != null) { - this.stepContext.setTraceId(traceId); - } - - long t1 = System.currentTimeMillis(); - - @SuppressWarnings("unchecked") - String validateMsg = this.inputValidate(input, - (Map)((Map)this.stepContext.get("input")).get("request")); - - this.stepContext.addElapsedTime("入参校验", System.currentTimeMillis()-t1); - - if (StringUtils.hasText(validateMsg)) { - long t2 = System.currentTimeMillis(); - // 将验证错误信息放入上下文 - stepContext.put("validateMsg", validateMsg); - Map validateResponse = config.getValidateResponse(); - // 数据转换 - AggregateResult aggregateResult = this.doInputDataMapping(input, validateResponse); - this.stepContext.addElapsedTime("入参校验结果转换", System.currentTimeMillis() - t2); - return Mono.just(aggregateResult); - } - - if(CollectionUtils.isEmpty(steps)) { - return handleOutput(input); - }else { - LinkedList opSteps = (LinkedList) steps.clone(); - Step step1 = opSteps.removeFirst(); - Mono> result = runStep(step1, null).expand(lastStepResponse -> { - if (opSteps.isEmpty() || lastStepResponse.isStop()) { - return Mono.empty(); - } - Step step = opSteps.pop(); - return runStep(step, lastStepResponse); - }).flatMap(response -> Flux.just(response)).collectList(); - return result.flatMap(clientResponse -> { - return handleOutput(input); - }); - } - } - - private Mono runStep(Step step, StepResponse lastStepResponse){ - StepResponse stepResponse = new StepResponse(step, null, new HashMap>()); - stepContext.put(step.getName(), stepResponse); - List components = step.getComponents(); - if (components != null && components.size() > 0) { - StepContextPosition stepCtxPos = new StepContextPosition(step.getName()); - return ComponentExecutor.exec(components, stepContext, stepCtxPos, (ctx, pos) -> { - step.beforeRun(stepContext, null); - return createStep(step); - }).flatMap(sr -> { - if (sr instanceof ComponentResult) { - return Mono.just(stepResponse); - } else { - return Mono.just((StepResponse) sr); - } - }); - } else { - step.beforeRun(stepContext, null); - return createStep(step); - } - } - - private Mono handleOutput(Input input){ - // 数据转换 - long t3 = System.currentTimeMillis(); - AggregateResult aggResult = this.doInputDataMapping(input, null); - this.stepContext.addElapsedTime(input.getName()+"聚合接口响应结果数据转换", System.currentTimeMillis() - t3); - if(this.stepContext.isDebug() || LOGGER.isDebugEnabled()) { - // LogService.setBizId(this.stepContext.getTraceId()); - ThreadContext.put(Consts.TRACE_ID, this.stepContext.getTraceId()); - String jsonString = JSON.toJSONString(aggResult); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("aggResult {}", jsonString); - LOGGER.debug("stepContext {}", JSON.toJSONString(stepContext)); - }else { - LOGGER.info("aggResult {}", jsonString); - LOGGER.info("stepContext {}", JSON.toJSONString(stepContext)); - } - } - return Mono.just(aggResult); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Mono createStep(Step step) { - long start = System.currentTimeMillis(); - List monos = step.run(); - Mono[] monoArray = monos.stream().toArray(Mono[]::new); - Monoresult = Flux.merge(monoArray).reduce(new HashMap(), (item1, item2) -> { - if (item2.isEmpty()) { - return item1; - } - Input input = (Input)item2.get("request"); - item1.put(input.getName() , item2.get("data")); - return item1; - }).flatMap(item -> { - // stepResult 数据转换 - long t1 = System.currentTimeMillis(); - StepResponse stepResponse = this.doStepDataMapping(step); - stepResponse.setStop(step.isStop()); - long t2 = System.currentTimeMillis(); - this.stepContext.addElapsedTime(step.getName() + "结果数据转换", t2 - t1); - this.stepContext.addElapsedTime(step.getName() + "耗时", System.currentTimeMillis() - start); - - return Mono.just(stepResponse); - }); - return result; - } - - /** - * 初始化上下文 - * @param clientInput 客户端提交上来的信息 - */ - public void initialStepContext(Map clientInput, ClientInputConfig config) { - Map input = new HashMap<>(); - Map inputRequest = new HashMap<>(); - Map inputResponse = new HashMap<>(); - input.put("request", inputRequest); - input.put("response", inputResponse); - if(clientInput != null) { - inputRequest.put("path", clientInput.get("path")); - inputRequest.put("method", clientInput.get("method")); - inputRequest.put("headers", clientInput.get("headers")); - inputRequest.put("params", clientInput.get("params")); - stepContext.addFilePartMap((Map) clientInput.get("filePartMap")); - - if (CONTENT_TYPE_XML.equals(config.getContentType()) || (StringUtils.isEmpty(config.getContentType()) - && isXmlContentType((String) clientInput.get("contentType")))) { - String[] paths = null; - if (!StringUtils.isEmpty(config.getXmlArrPaths())) { - paths = config.getXmlArrPaths().split(","); - } - Builder builder = new XmlToJson.Builder((String) clientInput.get("body")); - if (paths != null && paths.length > 0) { - for (int j = 0; j < paths.length; j++) { - String p = paths[j]; - builder = builder.forceList(p); - } - } - inputRequest.put("body", builder.build().toJson().toMap()); - } else if (clientInput.get("body") instanceof Map) { - inputRequest.put("body", clientInput.get("body")); - } else { - inputRequest.put("body", JSON.parse((String) clientInput.get("body"))); - } - } - stepContext.put("input", input); - } - - private boolean isXmlContentType(String contentType) { - if (contentType != null) { - String[] cts = contentType.split(";"); - for (int i = 0; i < cts.length; i++) { - if (CONTENT_TYPE_XML.equals(cts[i])) { - return true; - } - } - } - return false; - } - - @SuppressWarnings("unchecked") - private StepResponse doStepDataMapping(Step step) { - StepResponse stepResponse = (StepResponse)stepContext.get(step.getName()); - if (step.getDataMapping() != null) { - Map responseMapping = (Map) step.getDataMapping().get("response"); - if(responseMapping != null && !StringUtils.isEmpty(responseMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - - // body - stepResponse.setResult(PathMapping.transform(ctxNode, stepContext, - (Map) responseMapping.get("fixedBody"), - (Map) responseMapping.get("body"))); - - // script - if(responseMapping.get("script") != null) { - Map scriptCfg = (Map) responseMapping.get("script"); - try { - Map stepBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext, Map.class); - if(stepBody != null) { - stepResponse.setResult(stepBody); - } - } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - } - } - return stepResponse; - } - - /** - * 当validateResponse不为空表示验参失败,使用该配置响应数据 - */ - @SuppressWarnings("unchecked") - private AggregateResult doInputDataMapping(Input input, Map validateResponse) { - AggregateResult aggResult = new AggregateResult(); - Map> group = (Map>) stepContext.get("input"); - if(group == null) { - group = new HashMap>(); - stepContext.put("input", group); - } - Map response = null; - if(group.get("response") == null) { - response = new HashMap<>(); - group.put("response", response); - } - response = group.get("response"); - String respContentType = null; - int statusCode = 200; - if (input != null && input.getConfig() != null && input.getConfig().getDataMapping() != null) { - Map responseMapping = (Map) input.getConfig().getDataMapping() - .get("response"); - if (validateResponse != null) { - responseMapping = validateResponse; - } - if (!CollectionUtils.isEmpty(responseMapping)) { - respContentType = (String) responseMapping.get("contentType"); - ONode ctxNode = PathMapping.toONode(stepContext); - // HttpStatus - if (validateResponse == null) { - if (responseMapping.get("httpStatus") != null) { - Map fcMap = (Map) responseMapping.get("httpStatus"); - FieldConfig fc = new FieldConfig(fcMap); - if (ValueTypeEnum.FIXED.equals(fc.getType()) && fc.getValue() != null) { - statusCode = Integer.valueOf(fc.getValue().toString()); - } else if ((ValueTypeEnum.REF.equals(fc.getType()) || ValueTypeEnum.FUNC.equals(fc.getType())) - && fc.getValue() != null) { - String dataType = null; - if (fc.getRefDataType() != null) { - dataType = fc.getRefDataType().getCode(); - } - Object statusCodeObj = PathMapping.getValueByPath(ctxNode, dataType, - (String) fc.getValue()); - statusCode = (statusCodeObj == null) ? statusCode - : Integer.valueOf(statusCodeObj.toString()); - } - } - response.put("httpStatus", statusCode); - } - - // headers - Map headers = PathMapping.transform(ctxNode, stepContext, - MapUtil.upperCaseKey((Map) responseMapping.get("fixedHeaders")), - MapUtil.upperCaseKey((Map) responseMapping.get("headers")), false); - if (headers.containsKey(CommonConstants.WILDCARD_TILDE) - && headers.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { - response.put("headers", headers.get(CommonConstants.WILDCARD_TILDE)); - } else { - response.put("headers", headers); - } - - // body - Map body = PathMapping.transform(ctxNode, stepContext, - (Map) responseMapping.get("fixedBody"), - (Map) responseMapping.get("body")); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - response.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (responseMapping.get("script") != null) { - Map scriptCfg = (Map) responseMapping.get("script"); - try { - Object respBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if(respBody != null) { - body.putAll((Map) respBody); - } - } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - response.put("body", body); - } - } - } - - HttpHeaders httpHeaders = MapUtil.toHttpHeaders((Map) response.get("headers")); - if (CONTENT_TYPE_XML.equals(respContentType) && !httpHeaders.containsKey(CommonConstants.HEADER_CONTENT_TYPE)) { - httpHeaders.add(CommonConstants.HEADER_CONTENT_TYPE.toUpperCase(), CONTENT_TYPE_XML); - response.put(CommonConstants.HEADER_CONTENT_TYPE.toUpperCase(), CONTENT_TYPE_XML); - } - - // convert JSON to XML if it is XML content type - if(CONTENT_TYPE_XML.equals(respContentType)) { - Object respBody = response.get("body"); - response.put("jsonBody", respBody); - JsonToXml jsonToXml = new JsonToXml.Builder(JSON.toJSONString(respBody)).build(); - response.put("body", jsonToXml.toString()); - } - - Object respBody = response.get("body"); - // 测试模式返回StepContext - if (stepContext.returnContext() && respBody instanceof Map) { - Map t = (Map) respBody; - t.put(stepContext.CONTEXT_FIELD, stepContext); - } - - aggResult.setHttpStatus(statusCode); - aggResult.setBody(response.get("body")); - aggResult.setHeaders(httpHeaders); - return aggResult; - } - - private static final String LANGUAGE_CHINESE = "zh"; - private static final String LANGUAGE_ENGLISH = "en"; - enum ValidateType { - /** - * Header - */ - HEADER("请求头", "Header"), - /** - * Query param - */ - QUERY_PARAM("Query参数", "Query param"), - /** - * Body - */ - BODY("请求体", "Body"), - /** - * Script - */ - SCRIPT("脚本校验", "Script"); - - ValidateType(String tipZh, String tipEn) { - this.tipZh = tipZh; - this.tipEn = tipEn; - } - - String tipZh; - String tipEn; - - public String getTip() { - String language = I18nUtils.getContextLocale().getLanguage(); - if (LANGUAGE_CHINESE.equals(language)) { - return tipZh; - } else if (LANGUAGE_ENGLISH.equals(language)) { - return tipEn; - } - return tipZh; - } - } - - String inputValidate(Input input, Map clientInput) { - try { - InputConfig config = input.getConfig(); - if (config instanceof ClientInputConfig) { - Map langDef = ((ClientInputConfig) config).getLangDef(); - this.handleLangDef(langDef); - - Pair> validateTypeAndValidateErrorListPair = - this.doInputValidate((ClientInputConfig) config, clientInput); - if (validateTypeAndValidateErrorListPair == null) { - return null; - } - return String.format("%s: %s", validateTypeAndValidateErrorListPair.getFirst().getTip(), - StringUtils.collectionToCommaDelimitedString(validateTypeAndValidateErrorListPair.getSecond())); - } - return null; - } finally { - I18nUtils.removeContextLocale(); - } - } - - private Pair> doInputValidate(ClientInputConfig config, Map clientInput) { - Map headersDef = config.getHeadersDef(); - if (!CollectionUtils.isEmpty(headersDef)) { - // 验证headers入参是否符合要求 - List errorList; - PropertiesSupportUtils.setContextSupportPropertyUpperCase(); - try { - errorList = JsonSchemaUtils.validateAllowValueStr(JSON.toJSONString(headersDef), - JSON.toJSONString(clientInput.get("headers"))); - } finally { - PropertiesSupportUtils.removeContextSupportPropertyUpperCase(); - } - - if (!CollectionUtils.isEmpty(errorList)) { - return Pair.of(ValidateType.HEADER, errorList); - } - } - - Map paramsDef = config.getParamsDef(); - if (!CollectionUtils.isEmpty(paramsDef)) { - // 验证params入参是否符合要求 - List errorList = JsonSchemaUtils.validateAllowValueStr(JSON.toJSONString(paramsDef), - JSON.toJSONString(clientInput.get("params"))); - if (!CollectionUtils.isEmpty(errorList)) { - return Pair.of(ValidateType.QUERY_PARAM, errorList); - } - } - - Map bodyDef = config.getBodyDef(); - if (!CollectionUtils.isEmpty(bodyDef)) { - // 验证body入参是否符合要求 - List errorList = JsonSchemaUtils.validate(JSON.toJSONString(bodyDef), - JSON.toJSONString(clientInput.get("body"))); - if (!CollectionUtils.isEmpty(errorList)) { - return Pair.of(ValidateType.BODY, errorList); - } - } - - Map scriptValidate = config.getScriptValidate(); - if (!CollectionUtils.isEmpty(scriptValidate)) { - ONode ctxNode = PathMapping.toONode(stepContext); - // 验证入参是否符合脚本要求 - try { - @SuppressWarnings("unchecked") - List errorList = (List) ScriptHelper.execute(scriptValidate, ctxNode, stepContext, List.class); - if (!CollectionUtils.isEmpty(errorList)) { - return Pair.of(ValidateType.SCRIPT, errorList); - } - } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptValidate), e); - throw new ExecuteScriptException(e, stepContext, scriptValidate); - } - } - - return null; - } - - @SuppressWarnings("unchecked") - private void handleLangDef(Map langDef) { - if (!CollectionUtils.isEmpty(langDef)) { - // 存在提示语言定义配置 - Object langParamObj = langDef.get("langParam"); - String langParam = null; - if (langParamObj instanceof String) { - langParam = (String) langParamObj; - } - Object langMappingObj = langDef.get("langMapping"); - Map langMapping = null; - if (langMappingObj instanceof Map) { - langMapping = (Map) langMappingObj; - } - if (langParam != null && !CollectionUtils.isEmpty(langMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - Map langParamMap = new HashMap<>(2); - langParamMap.put("langParam", langParam); - Map transformMap = PathMapping.transform(ctxNode, stepContext, null, langParamMap); - Object langParamValue = transformMap.get("langParam"); - if (langParamValue != null) { - // 判断使用哪种语言 - Object zh = langMapping.get("zh"); - if (zh != null && zh.toString().equals(langParamValue.toString())) { - I18nUtils.setContextLocale(new Locale("zh")); - } - Object en = langMapping.get("en"); - if (en != null && en.toString().equals(langParamValue.toString())) { - I18nUtils.setContextLocale(new Locale("en")); - } - } - } - } - } - - public void setApplicationContext(ConfigurableApplicationContext appContext) { - this.applicationContext = appContext; - } - - public ConfigurableApplicationContext getApplicationContext() { - return this.applicationContext; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/Step.java b/fizz-core/src/main/java/com/fizzgate/fizz/Step.java deleted file mode 100644 index a691aaa..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/Step.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import java.lang.ref.SoftReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.collections.CollectionUtils; -import org.springframework.context.ConfigurableApplicationContext; - -import com.fizzgate.fizz.component.ComponentExecutor; -import com.fizzgate.fizz.component.ComponentResult; -import com.fizzgate.fizz.component.IComponent; -import com.fizzgate.fizz.component.StepContextPosition; -import com.fizzgate.fizz.input.Input; -import com.fizzgate.fizz.input.InputConfig; -import com.fizzgate.fizz.input.InputContext; -import com.fizzgate.fizz.input.InputFactory; -import com.fizzgate.fizz.input.InputType; - -import reactor.core.publisher.Mono; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class Step { - private SoftReference weakPipeline; - private String name; - - // 是否在执行完当前step就返回 - private boolean stop; - - private Map dataMapping; - - private Map requestConfigs = new HashMap(); - - private List components; - - public List getComponents() { - return components; - } - - public void setComponents(List components) { - this.components = components; - } - - public SoftReference getWeakPipeline() { - return weakPipeline; - } - - public void setWeakPipeline(SoftReference weakPipeline) { - this.weakPipeline = weakPipeline; - } - - public ConfigurableApplicationContext getCurrentApplicationContext() { - return this.getWeakPipeline() != null ? this.getWeakPipeline().get().getApplicationContext(): null; - } - - public static class Builder { - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Step read(Map config, SoftReference weakPipeline) { - Step step = new Step(); - step.setWeakPipeline(weakPipeline); - List requests = (List) config.get("requests"); - if (CollectionUtils.isNotEmpty(requests)) { - for (Map requestConfig : requests) { - InputConfig inputConfig = InputFactory.createInputConfig(requestConfig); - step.addRequestConfig((String) requestConfig.get("name"), inputConfig); - } - } - step.setComponents(ComponentExecutor.buildComponents((List>) config.get("components"))); - return step; - } - } - - private StepContext stepContext; - - public StepContext getStepContext(){ - return this.stepContext; - } - - private StepResponse lastStepResponse = null; - private Map inputs = new HashMap(); - public void beforeRun(StepContext stepContext2, StepResponse response ) { - stepContext = stepContext2; - lastStepResponse = response; - StepResponse stepResponse = (StepResponse) stepContext.get(this.name); - Map configs = this.getRequestConfigs(); - for(String configName :configs.keySet()) { - InputConfig inputConfig = configs.get(configName); - InputType type = inputConfig.getType(); - Input input = InputFactory.createInput(type.toString()); - input.setWeakStep(new SoftReference(this)); - input.setConfig(inputConfig); - input.setName(configName); - input.setStepResponse(stepResponse); - InputContext context = new InputContext(stepContext, lastStepResponse); - input.beforeRun(context); - inputs.put(input.getName(), input); - } - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public List run() { - List monos = new ArrayList(); - for(String requestName :inputs.keySet()) { - Input input = inputs.get(requestName); - List components = input.getConfig().getComponents(); - if (components != null && components.size() > 0) { - StepContextPosition stepCtxPos = new StepContextPosition(name, requestName); - Mono result = ComponentExecutor.exec(components, stepContext, stepCtxPos, (ctx, pos) -> { - if (input.needRun(ctx)) { - return input.run(); - } - Map inputResult = new HashMap(); - inputResult.put("data", new HashMap()); - inputResult.put("request", input); - return Mono.just(inputResult); - }).flatMap(r -> { - if (r instanceof ComponentResult) { - Map inputResult = new HashMap(); - inputResult.put("data", new HashMap()); - inputResult.put("request", input); - return Mono.just(inputResult); - } else { - return Mono.just(r); - } - }); - monos.add(result); - } else { - if (input.needRun(stepContext)) { - Mono singleMono = input.run(); - monos.add(singleMono); - } - } - } - return monos; - } - - - - public void afeterRun() { - - } - - public InputConfig addRequestConfig(String name, InputConfig requestConfig) { - return requestConfigs.put(name, requestConfig); - } - - - public Map getRequestConfigs() { - return requestConfigs; - } - - - public String getName() { - if (name == null) { - return name = "step" + (int)(Math.random()*100); - } - return name; - } - - - public void setName(String name) { - this.name = name; - } - - public boolean isStop() { - return stop; - } - - public void setStop(boolean stop) { - this.stop = stop; - } - - public Map getDataMapping() { - return dataMapping; - } - - public void setDataMapping(Map dataMapping) { - this.dataMapping = dataMapping; - } - - -} - diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/StepContext.java b/fizz-core/src/main/java/com/fizzgate/fizz/StepContext.java deleted file mode 100644 index 3acce25..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/StepContext.java +++ /dev/null @@ -1,892 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.http.codec.multipart.FilePart; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.constants.CommonConstants; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - * @param - * @param - */ -@SuppressWarnings("unchecked") -public class StepContext extends ConcurrentHashMap { - private ConfigurableApplicationContext applicationContext; - public static final String ELAPSED_TIMES = "elapsedTimes"; - public static final String DEBUG = "debug"; - public static final String RETURN_CONTEXT = "returnContext"; - // context field in response body - public static final String CONTEXT_FIELD = "_context"; - - // exception info - public static final String EXCEPTION_MESSAGE = "exceptionMessage"; - public static final String EXCEPTION_STACKS = "exceptionStacks"; - public static final String EXCEPTION_DATA = "exceptionData"; - - private Map filePartMap = new HashMap<>(); - - public void setDebug(Boolean debug) { - this.put((K)DEBUG, (V)debug); - } - - public String getTraceId() { - return (String) this.get(CommonConstants.TRACE_ID); - } - - public void setTraceId(String traceId) { - this.put((K)CommonConstants.TRACE_ID, (V)traceId); - } - - /** - * 是否调试模式 - * @return - */ - public Boolean isDebug() { - return this.get(DEBUG) == null ? false : (Boolean) this.get(DEBUG); - } - - /** - * 是否在响应体里返回上下文 - * @return - */ - public boolean returnContext() { - return Boolean.valueOf((String)getInputReqHeader(RETURN_CONTEXT)); - } - - public void addFilePart(String key, FilePart filePart) { - this.filePartMap.put(key, filePart); - } - - public void addFilePartMap(Map filePartMap) { - if(filePartMap != null && !filePartMap.isEmpty()) { - this.filePartMap.putAll(filePartMap); - } - } - - public FilePart getFilePart(String key) { - return this.filePartMap.get(key); - } - - public Map getFilePartMap() { - return this.filePartMap; - } - - /** - * set exception information - * @param cause exception - * @param exceptionData data that cause the exception, such as script source code, etc. - */ - public void setExceptionInfo(Throwable cause, Object exceptionData) { - this.put((K) EXCEPTION_MESSAGE, (V) cause.getMessage()); - this.put((K) EXCEPTION_DATA, (V) exceptionData); - - StackTraceElement[] stacks = cause.getStackTrace(); - if (stacks != null && stacks.length > 0) { - String[] arr = new String[stacks.length]; - for (int i = 0; i < stacks.length; i++) { - StackTraceElement ste = stacks[i]; - StringBuffer sb = new StringBuffer(); - sb.append(ste.getClassName()).append(".").append(ste.getMethodName()).append("(") - .append(ste.getFileName()).append(":").append(ste.getLineNumber()).append(")"); - arr[i] = sb.toString(); - } - this.put((K) EXCEPTION_STACKS, (V) arr); - } - } - - public synchronized void addElapsedTime(String actionName, Long milliSeconds) { - List> elapsedTimes = (List>) this.get(ELAPSED_TIMES); - if (elapsedTimes == null) { - elapsedTimes = new ArrayList>(); - this.put((K) ELAPSED_TIMES, (V) elapsedTimes); - } - Map record = new HashMap<>(); - record.put(actionName, milliSeconds); - elapsedTimes.add(record); - } - - public V getElapsedTimes() { - return this.get(ELAPSED_TIMES); - } - - private Map getStepRequest(String stepName, String requestName) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return null; - } - Map> requests = (Map>) stepResponse.getRequests(); - if (requests == null) { - requests = new HashMap<>(); - stepResponse.setRequests(requests); - requests.put(requestName, new HashMap()); - }else if(!requests.containsKey(requestName)) { - requests.put(requestName, new HashMap()); - } - return (Map) requests.get(requestName); - } - - /** - * 设置Step里调用接口的请求头 - * - * @param stepName - * @param requestName - * @param headerName - * @param headerValue - */ - public void setStepReqHeader(String stepName, String requestName, String headerName, Object headerValue) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - Map req = (Map) request.get("request"); - if (req == null) { - req = new HashMap<>(); - request.put("request", req); - } - Map headers = (Map) req.get("headers"); - if (headers == null) { - headers = new HashMap<>(); - req.put("headers", headers); - } - if (headerName == null || "".equals(headerName)) { - return; - } - headers.put(headerName.toUpperCase(), headerValue); - } - - /** - * 获取Step里调用接口的请求头 - * - * @param stepName - * @param requestName - * @param headerName - */ - public Object getStepReqHeader(String stepName, String requestName, String headerName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map req = (Map) request.get("request"); - if (req == null) { - return null; - } - Map headers = (Map) req.get("headers"); - if (headers == null) { - return null; - } - if (headerName == null || "".equals(headerName)) { - return null; - } - return headers.get(headerName.toUpperCase()); - } - - /** - * 设置Step里调用接口的请求body - * - * @param stepName - * @param requestName - * @param key - * @param value - */ - public void setStepReqBody(String stepName, String requestName, String key, Object value) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - Map req = (Map) request.get("request"); - if (req == null) { - req = new HashMap<>(); - request.put("request", req); - } - if (req.get("body") != null && !(req.get("body") instanceof Map)) { - return; - } - Map body = (Map) req.get("body"); - if (body == null) { - body = new HashMap<>(); - req.put("body", body); - } - body.put(key, value); - } - - /** - * 获取Step里调用接口的请求body - * - * @param stepName - * @param requestName - * @param fieldName - */ - public Object getStepReqBody(String stepName, String requestName, String fieldName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map req = (Map) request.get("request"); - if (req == null) { - req = new HashMap<>(); - request.put("request", req); - } - if (req.get("body") != null && !(req.get("body") instanceof Map)) { - return null; - } - Map body = (Map) req.get("body"); - if (body == null) { - return null; - } - return body.get(fieldName); - } - - /** - * 获取Step里调用接口的请求body - * - * @param stepName - * @param requestName - */ - public Object getStepReqBody(String stepName, String requestName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map req = (Map) request.get("request"); - if (req == null) { - req = new HashMap<>(); - request.put("request", req); - } - return req.get("body"); - } - - /** - * 获取Step里调用的接口的URL参数 - * @param stepName 步骤名【必填】 - * @param requestName 请求的接口名 【必填】 - * @param paramName URL参数名 【选填】,不传时返回所有URL参数 - */ - public Object getStepReqParam(String stepName, String requestName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map req = (Map) request.get("request"); - if (req == null) { - req = new HashMap<>(); - request.put("request", req); - } - return req.get("params"); - } - - /** - * 获取Step里调用的接口的URL参数 - * @param stepName 步骤名【必填】 - * @param requestName 请求的接口名 【必填】 - * @param paramName URL参数名 【必填】 - */ - public Object getStepReqParam(String stepName, String requestName, String paramName) { - Map params = (Map) this.getStepReqParam(stepName, requestName); - return params == null ? null : params.get(paramName); - } - /** - * 设置Step里调用接口响应头 - * - * @param stepName - * @param requestName - * @param headerName - * @param headerValue - */ - public void setStepRespHeader(String stepName, String requestName, String headerName, Object headerValue) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - Map response = (Map) request.get("response"); - if (response == null) { - response = new HashMap<>(); - request.put("response", response); - } - Map headers = (Map) response.get("headers"); - if (headers == null) { - headers = new HashMap<>(); - response.put("headers", headers); - } - if (headerName == null || "".equals(headerName)) { - return; - } - headers.put(headerName.toUpperCase(), headerValue); - } - - /** - * 获取Step里调用接口响应头 - * - * @param stepName - * @param requestName - * @param headerName - */ - public Object getStepRespHeader(String stepName, String requestName, String headerName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map response = (Map) request.get("response"); - if (response == null) { - return null; - } - Map headers = (Map) response.get("headers"); - if (headers == null) { - return null; - } - if (headerName == null || "".equals(headerName)) { - return null; - } - return headers.get(headerName.toUpperCase()); - } - - /** - * 设置Step里调用接口的响应body - * - * @param stepName - * @param requestName - * @param key - * @param value - */ - public void setStepRespBody(String stepName, String requestName, String key, Object value) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - Map response = (Map) request.get("response"); - if (response == null) { - response = new HashMap<>(); - request.put("response", response); - } - if (response.get("body") != null && !(response.get("body") instanceof Map)) { - return; - } - Map body = (Map) response.get("body"); - if (body == null) { - body = new HashMap<>(); - response.put("body", body); - } - body.put(key, value); - } - - /** - * 获取Step里调用接口的响应body - * - * @param stepName - * @param requestName - * @param key - */ - public Object getStepRespBody(String stepName, String requestName, String key) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map response = (Map) request.get("response"); - if (response == null) { - return null; - } - if (response.get("body") != null && !(response.get("body") instanceof Map)) { - return null; - } - Map body = (Map) response.get("body"); - if (body == null) { - return null; - } - return body.get(key); - } - - /** - * 获取Step里调用接口的响应body - * - * @param stepName - * @param requestName - */ - public Object getStepRespBody(String stepName, String requestName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - Map response = (Map) request.get("response"); - if (response == null) { - return null; - } - return response.get("body"); - } - - /** - * 设置Step的结果 - * - * @param stepName - * @param key - * @param value - */ - public void setStepResult(String stepName, String key, Object value) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return; - } - Map result = (Map) stepResponse.getResult(); - if (result == null) { - result = new HashMap<>(); - stepResponse.setResult(result); - } - result.put(key, value); - } - - /** - * 获取Step的结果 - * - * @param stepName - * @param key - */ - public Object getStepResult(String stepName, String key) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return null; - } - Map result = (Map) stepResponse.getResult(); - if (result == null) { - return null; - } - return result.get(key); - } - - /** - * 获取Step的结果 - * - * @param stepName - */ - public Object getStepResult(String stepName) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return null; - } - return stepResponse.getResult(); - } - - /** - * 设置聚合接口的响应头 - * - * @param headerName - * @param headerValue - */ - public void setInputRespHeader(String headerName, Object headerValue) { - Map input = (Map) this.get("input"); - if (input == null) { - return; - } - Map response = (Map) input.get("response"); - if (response == null) { - return; - } - Map headers = (Map) response.get("headers"); - if (headers == null) { - headers = new HashMap<>(); - response.put("headers", headers); - } - headers.put(headerName, headerValue); - } - - /** - * 获取聚合接口的响应头 - * - * @param headerName - */ - public Object getInputRespHeader(String headerName) { - Map input = (Map) this.get("input"); - if (input == null) { - return null; - } - Map response = (Map) input.get("response"); - if (response == null) { - return null; - } - Map headers = (Map) response.get("headers"); - if (headers == null) { - return null; - } - return headers.get(headerName); - } - - /** - * 获取聚合接口的请求头 - * - * @param headerName - */ - public Object getInputReqHeader(String headerName) { - if (headerName == null || "".equals(headerName)) { - return null; - } - Map input = (Map) this.get("input"); - if (input == null) { - return null; - } - Map request = (Map) input.get("request"); - if (request == null) { - return null; - } - Map headers = (Map) request.get("headers"); - if (headers == null) { - return null; - } - return headers.get(headerName.toUpperCase()); - } - - /** - * 设置聚合接口的响应body - * - * @param fieldName - * @param value - */ - public void setInputRespBody(String fieldName, Object value) { - Map input = (Map) this.get("input"); - if (input == null) { - return; - } - Map response = (Map) input.get("response"); - if (response == null) { - response = new HashMap<>(); - input.put("response", response); - } - if (response.get("body") != null && !(response.get("body") instanceof Map)) { - return; - } - Map body = (Map) response.get("body"); - if (body == null) { - body = new HashMap<>(); - response.put("body", body); - } - body.put(fieldName, value); - } - - /** - * 获取聚合接口的响应body - * - * @param fieldName - */ - public Object getInputRespBody(String fieldName) { - Map input = (Map) this.get("input"); - if (input == null) { - return null; - } - Map response = (Map) input.get("response"); - if (response == null) { - return null; - } - if (response.get("body") != null && !(response.get("body") instanceof Map)) { - return null; - } - Map body = (Map) response.get("body"); - if (body == null) { - return null; - } - return body.get(fieldName); - } - - /** - * 获取聚合接口的响应body - * - */ - public Object getInputRespBody() { - Map input = (Map) this.get("input"); - if (input == null) { - return null; - } - Map response = (Map) input.get("response"); - if (response == null) { - return null; - } - return response.get("body"); - } - - /** - * 获取聚合接口的请求body - * - * @param fieldName - */ - @SuppressWarnings("unused") - public Object getInputReqBody(String fieldName) { - Object respBody = getInputReqAttr("body"); - if (respBody != null && !(respBody instanceof Map)) { - return null; - } - Map body = (Map) respBody; - if (body == null) { - return null; - } - return body.get(fieldName); - } - - /** - * 获取聚合接口的请求body - * - */ - public Object getInputReqBody() { - return getInputReqAttr("body"); - } - - /** - * 获取客户端URL请求参数(query string) - */ - public Object getInputReqParam() { - return this.getInputReqAttr("params"); - } - - /** - * 获取客户端URL请求参数(query string) - * @param paramName URL参数名 - */ - public Object getInputReqParam(String paramName) { - Map params = (Map) this.getInputReqAttr("params"); - return params == null ? null : paramName == null ? params : params.get(paramName); - } - - /** - * 获取聚合接口请求属性
- * 可选属性:path,method,headers,params,body - * - */ - public Object getInputReqAttr(String key) { - Map input = (Map) this.get("input"); - if (input == null) { - return null; - } - Map request = (Map) input.get("request"); - if (request == null) { - return null; - } - return request.get(key); - } - - /** - * 设置Step的循环对象
- * Set the current circle item of step
- *
- * - * @param stepName - * @param item - * @param index - */ - public void setStepCircleItem(String stepName, Object item, Integer index) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return; - } - stepResponse.setItem(item); - stepResponse.setIndex(index); - } - - /** - * 添加Step的循环结果
- * Add the result of current circle item
- *
- * - * @param stepName - * @param key - * @param value - */ - public void addStepCircleResult(String stepName) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return; - } - List> circle = (List>) stepResponse.getCircle(); - if (circle == null) { - circle = new ArrayList<>(); - stepResponse.setCircle(circle); - } - Map circleResult = new HashMap<>(); - circleResult.put("requests", deepCopy(stepResponse.getRequests())); - circleResult.put("result", deepCopy(stepResponse.getResult())); - circleResult.put("item", deepCopy(stepResponse.getItem())); - circleResult.put("index", stepResponse.getIndex()); - circle.add(circleResult); - } - - /** - * 获取Step的循环对象
- * Returns current circle item
- * - * @param stepName - */ - public Object getStepItem(String stepName) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return null; - } - return stepResponse.getItem(); - } - - /** - * 获取Step的循环结果
- * Returns circle result list of step
- * - * @param stepName - */ - public List> getStepCircle(String stepName) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return null; - } - return stepResponse.getCircle(); - } - - /** - * 设置请求的循环对象
- * Set current circle item of request
- * - * @param stepName - * @param requestName - * @param item - */ - public void setRequestCircleItem(String stepName, String requestName, Object item, Integer index) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - request.put("item", item); - request.put("index", index); - } - - /** - * 设置请求的循环结果
- * Set current circle result of request
- * - * @param stepName - * @param requestName - */ - public void addRequestCircleResult(String stepName, String requestName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - List> circle = (List>) request.get("circle"); - if (circle == null) { - circle = new ArrayList<>(); - request.put("circle", circle); - } - Map circleResult = new HashMap<>(); - circleResult.put("request", deepCopy(request.get("request"))); - circleResult.put("response", deepCopy(request.get("response"))); - circleResult.put("item", deepCopy(request.get("item"))); - circleResult.put("index", request.get("index")); - circle.add(circleResult); - } - - /** - * 设置判断条件组件结果
- * Set result of condition components
- * - * @param stepName step name - * @param requestName request name - * @param conditionDesc condition description, such as:
- * circle[0]-execCondition:my condition 1
- * circle[1]-breakCondition:my condition 2
- * condition:my condition 3
- * @param rs result of condition component - */ - public void addConditionResult(String stepName, String requestName, String conditionDesc, boolean rs) { - if (requestName == null) { - StepResponse stepResponse = (StepResponse) this.get(stepName); - if (stepResponse == null) { - return; - } - List> results = (List>) stepResponse.getConditionResults(); - if (results == null) { - results = new ArrayList<>(); - stepResponse.setConditionResults(results); - } - Map result = new HashMap<>(); - result.put(conditionDesc, rs); - results.add(result); - } else { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return; - } - List> results = (List>) request.get("conditionResults"); - if (results == null) { - results = new ArrayList<>(); - request.put("conditionResults", results); - } - Map result = new HashMap<>(); - result.put(conditionDesc, rs); - results.add(result); - } - } - - /** - * 获取请求的循环对象
- * Returns the current circle item of request
- * - * @param stepName - * @param requestName - */ - public Object getRequestCircleItem(String stepName, String requestName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - return request.get("item"); - } - - /** - * 获取请求的循环结果
- * Returns circle result list of request
- * - * @param stepName - * @param requestName - */ - public List> getRequestCircle(String stepName, String requestName) { - Map request = getStepRequest(stepName, requestName); - if (request == null) { - return null; - } - return (List>) request.get("circle"); - } - - private Object deepCopy(Object obj) { - if(obj == null) { - return obj; - } - if(obj.getClass().isPrimitive()) { - return obj; - } - return JSON.parse(JSON.toJSONString(obj)); - } - - public ConfigurableApplicationContext getApplicationContext(){ - return this.applicationContext; - } - - public void setApplicationContext(ConfigurableApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/StepResponse.java b/fizz-core/src/main/java/com/fizzgate/fizz/StepResponse.java deleted file mode 100644 index ba32083..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/StepResponse.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * @author linwaiwai - */ -public class StepResponse { - private String stepName; - private Map> requests; - private Map result; - private boolean stop; - // circle item - private Object item; - // index of circle item - private Integer index; - // circle results - private List> circle; - // result of condition components - private List> conditionResults; - - public StepResponse(Step aStep, HashMap item, Map> requests) { - setStepName(aStep.getName()); - setResult(item); - setRequests(requests); - } - public StepResponse(Step aStep, HashMap item) { - setStepName(aStep.getName()); - setResult(item); - } - - public void addRequest(String requestName, Map requestObj) { - if (this.requests.containsKey(requestName)) { - this.requests.get(requestName).putAll(requestObj); - } else { - this.requests.put(requestName, requestObj); - } - } - - public boolean isStop() { - return stop; - } - public void setStop(boolean stop) { - this.stop = stop; - } - public String getStepName() { - return stepName; - } - public void setStepName(String stepName) { - this.stepName = stepName; - } - public Map> getRequests() { - return requests; - } - public void setRequests(Map> requests) { - this.requests = requests; - } - public Map getResult() { - return result; - } - public void setResult(Map result) { - this.result = result; - } - public Object getItem() { - return item; - } - public void setItem(Object item) { - this.item = item; - } - public List> getCircle() { - return circle; - } - public void setCircle(List> circle) { - this.circle = circle; - } - public Integer getIndex() { - return index; - } - public void setIndex(Integer index) { - this.index = index; - } - public List> getConditionResults() { - return conditionResults; - } - public void setConditionResults(List> conditionResults) { - this.conditionResults = conditionResults; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentExecutor.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentExecutor.java deleted file mode 100644 index c6ff6e0..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentExecutor.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; - -import org.noear.snack.ONode; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.component.circle.Circle; -import com.fizzgate.fizz.component.condition.Condition; - -import reactor.core.publisher.Mono; - -/** - * Condition component - * - * @author Francis Dong - * - */ -public class ComponentExecutor { - - /** - * Converts step context to ONode - * - * @param stepContext context - * @return - */ - public static ONode toONode(StepContext stepContext) { - ONode o = null; - synchronized (stepContext) { - o = ONode.loadObj(stepContext); - } - return o; - } - - public static List buildComponents(List> componentConfig) { - List components = new ArrayList<>(); - - if (componentConfig != null && componentConfig.size() > 0) { - for (Map m : componentConfig) { - // condition - if (ComponentTypeEnum.CONDITION.getCode().equals(m.get("type"))) { - Condition c = JSON.parseObject(JSON.toJSONString(m), Condition.class); - components.add(c); - } - - // circle - if (ComponentTypeEnum.CIRCLE.getCode().equals(m.get("type"))) { - Circle c = JSON.parseObject(JSON.toJSONString(m), Circle.class); - components.add(c); - } - } - } - - return components; - } - - /** - * - * @param components - * @param stepContext - * @param f - */ - public static Mono exec(List components, StepContext stepContext, - StepContextPosition stepCtxPos, BiFunction f) { - if (components != null && components.size() > 0) { - // conditions before circle component - List conditions = new ArrayList<>(); - Circle circle = null; - for (IComponent component : components) { - if (ComponentTypeEnum.CIRCLE == component.getType()) { - circle = (Circle) component; - } - if (circle == null && ComponentTypeEnum.CONDITION == component.getType()) { - conditions.add((Condition) component); - } - } - - if (conditions != null && conditions.size() > 0) { - ONode ctxNode = toONode(stepContext); - for (Condition c : conditions) { - boolean rs = c.exec(ctxNode); - stepContext.addConditionResult(stepCtxPos.getStepName(), stepCtxPos.getRequestName(), c.getDesc(), - rs); - if (!rs) { - return Mono.just(new ComponentResult()); - } - } - } - - if (circle != null) { - return circle.exec(stepContext, stepCtxPos, f); - } else { - return f.apply(stepContext, stepCtxPos); - } - } - return Mono.just(new ComponentResult()); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentResult.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentResult.java deleted file mode 100644 index ae853f2..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentResult.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.fizzgate.fizz.component; - -public class ComponentResult { - - public ComponentResult() { - - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentTypeEnum.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentTypeEnum.java deleted file mode 100644 index fd33de9..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/ComponentTypeEnum.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.component; - -/** - * Component Type - * - * @author Francis Dong - * - */ -public enum ComponentTypeEnum { - - CONDITION("condition"), CIRCLE("circle"); - - private String code; - - private ComponentTypeEnum(String code) { - this.code = code; - } - - public static ComponentTypeEnum getEnumByCode(String code) { - for (ComponentTypeEnum item : ComponentTypeEnum.values()) { - if (item.getCode().equals(code)) { - return item; - } - } - - return null; - } - - public String getCode() { - return code; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/IComponent.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/IComponent.java deleted file mode 100644 index 39901c8..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/IComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.component; - -/** - * Component interface - * - * @author Francis Dong - * - */ -public interface IComponent { - - /** - * Returns component type - * @return - */ - public ComponentTypeEnum getType(); - - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/OperatorEnum.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/OperatorEnum.java deleted file mode 100644 index 3c4b81e..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/OperatorEnum.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.component; - -/** - * Operator - * - * @author Francis Dong - * - */ -public enum OperatorEnum { - - EQ("eq"), NE("ne"), GT("gt"), GE("ge"), LT("lt"), LE("le"), CONTAINS("contains"), NOTCONTAIN("notContain"), CONTAINSANY("containsAny"), - ISNULL("isNull"), ISNOTNULL("isNotNull"), ISBLANK("isBlank"), ISNOTBLANK("isNotBlank"), ISEMPTY("isEmpty"), - ISNOTEMPTY("isNotEmpty"); - - private String code; - - private OperatorEnum(String code) { - this.code = code; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/StepContextPosition.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/StepContextPosition.java deleted file mode 100644 index 879111c..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/StepContextPosition.java +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.component; - -import lombok.Data; - -/** - * - * @author Francis Dong - * - */ -@Data -public class StepContextPosition { - - private String stepName; - private String requestName; - - public StepContextPosition(String stepName) { - this.stepName = stepName; - } - - public StepContextPosition(String stepName, String requestName) { - this.stepName = stepName; - this.requestName = requestName; - } - - public String getPath() { - if (this.requestName == null) { - return this.stepName; - } - return this.stepName + ".requests." + this.requestName; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/circle/Circle.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/circle/Circle.java deleted file mode 100644 index 8382d56..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/circle/Circle.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.component.circle; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.BiFunction; - -import org.noear.snack.ONode; - -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.component.ComponentExecutor; -import com.fizzgate.fizz.component.ComponentResult; -import com.fizzgate.fizz.component.ComponentTypeEnum; -import com.fizzgate.fizz.component.IComponent; -import com.fizzgate.fizz.component.StepContextPosition; -import com.fizzgate.fizz.component.condition.Condition; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.field.ValueTypeEnum; -import com.fizzgate.fizz.input.PathMapping; - -import lombok.Data; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * Circle component - * - * @author Francis Dong - * - */ -public class Circle implements IComponent { - - private static final String type = ComponentTypeEnum.CIRCLE.getCode(); - - private String desc; - - private ValueTypeEnum dataSourceType; - - private Object dataSource; - - private List execConditions; - - private List breakConditions; - - @Override - public ComponentTypeEnum getType() { - return ComponentTypeEnum.getEnumByCode(type); - } - - /** - * - * @param desc [optional] description - * @param dataSourceType [required] type of data source - * @param dataSource [required] data source - * @param execConditions [optional] conditions to execute current circle loop - * item - * @param breakConditions [optional] conditions to break circle - */ - public Circle(String desc, ValueTypeEnum dataSourceType, Object dataSource, List execConditions, - List breakConditions) { - this.desc = desc; - this.dataSourceType = dataSourceType; - this.dataSource = dataSource; - this.execConditions = execConditions; - this.breakConditions = breakConditions; - } - - /** - * Current item - */ - private Object currentItem; - - /** - * Index of current item - */ - private Integer index; - - /** - * Fixed value of dataSource - */ - private Integer fixedValue; - - /** - * Reference value of dataSource - */ - private Object refValue; - - private boolean refReadFlag; - - private Integer getFixedValue(ONode ctxNode) { - if (fixedValue != null) { - return fixedValue; - } - if (dataSource == null) { - return fixedValue; - } - if (dataSource instanceof Integer || dataSource instanceof Long || dataSource instanceof String) { - try { - fixedValue = Integer.valueOf(dataSource.toString()); - } catch (Exception e) { - throw new FizzRuntimeException("invalid data source, fixed data source must be a positive integer"); - } - if (fixedValue.intValue() < 1) { - throw new FizzRuntimeException("invalid data source, fixed data source must be a positive integer"); - } - return fixedValue; - } else { - throw new FizzRuntimeException("invalid data source, fixed data source must be a positive integer"); - } - } - - @SuppressWarnings("unchecked") - private Object getRefValue(ONode ctxNode) { - if (refReadFlag) { - return refValue; - } - Object value = PathMapping.getValueByPath(ctxNode, (String) dataSource); - if (value == null) { - return null; - } - if (value instanceof Collection) { - refValue = (List) value; - return refValue; - } else if (value instanceof Integer || value instanceof Long || value instanceof String) { - try { - Integer times = Integer.valueOf(value.toString()); - if (times.intValue() < 1) { - throw new FizzRuntimeException( - "invalid data source, data source must be a positive integer or an array"); - } - refValue = times; - } catch (FizzRuntimeException e) { - throw e; - } catch (Exception e) { - throw new FizzRuntimeException( - "invalid data source, data source must be a positive integer or an array"); - } - return refValue; - } else { - throw new FizzRuntimeException( - "invalid data source, referenced data source must be a positive integer or an array"); - } - } - - /** - * Returns next circle item, returns null if no item left or dataSource is null - * - * @return - */ - @SuppressWarnings("unchecked") - public CircleItem next(ONode ctxNode) { - if (ValueTypeEnum.FIXED.equals(dataSourceType)) { - Integer total = this.getFixedValue(ctxNode); - if (index == null) { - index = 0; - currentItem = index + 1; - return new CircleItem(currentItem, index); - } else if (index.intValue() < total.intValue() - 1) { - index = index + 1; - currentItem = index + 1; - return new CircleItem(currentItem, index); - } else { - return null; - } - } else if (ValueTypeEnum.REF.equals(dataSourceType)) { - Object refValue = this.getRefValue(ctxNode); - if (refValue instanceof Collection) { - List list = (List) refValue; - if (index == null) { - index = 0; - currentItem = list.get(index); - return new CircleItem(currentItem, index); - } else if (index.intValue() < list.size() - 1) { - index = index + 1; - currentItem = list.get(index); - return new CircleItem(currentItem, index); - } else { - return null; - } - } else if (refValue instanceof Integer) { - Integer total = (Integer) refValue; - if (index == null) { - index = 0; - currentItem = index + 1; - return new CircleItem(currentItem, index); - } else if (index.intValue() < total.intValue() - 1) { - index = index + 1; - currentItem = index + 1; - return new CircleItem(currentItem, index); - } else { - return null; - } - } - } - return null; - } - - /** - * Returns true if execConditions are all true, false otherwise - * - * @param ctxNode - * @return - */ - public boolean canExec(int index, ONode ctxNode, StepContext stepContext, - StepContextPosition stepCtxPos) { - if (this.execConditions != null && this.execConditions.size() > 0) { - try { - for (Condition c : execConditions) { - boolean rs = c.exec(ctxNode); - stepContext.addConditionResult(stepCtxPos.getStepName(), stepCtxPos.getRequestName(), - "circle[" + index + "]-execCondition:" + c.getDesc(), rs); - if (!rs) { - return false; - } - } - } catch (FizzRuntimeException e) { - throw new FizzRuntimeException(type + " " + e.getMessage(), e.getCause()); - } - } - return true; - } - - /** - * Returns true if breakConditions are all true, false otherwise - * - * @param ctxNode - * @return - */ - public boolean breakCircle(int index, ONode ctxNode, StepContext stepContext, - StepContextPosition stepCtxPos) { - if (this.breakConditions != null && this.breakConditions.size() > 0) { - try { - for (Condition c : breakConditions) { - boolean rs = c.exec(ctxNode); - stepContext.addConditionResult(stepCtxPos.getStepName(), stepCtxPos.getRequestName(), - "circle[" + index + "]-breakCondition:" + c.getDesc(), rs); - if (rs) { - return true; - } - } - } catch (FizzRuntimeException e) { - throw new FizzRuntimeException(type + " " + e.getMessage(), e.getCause()); - } - } - return false; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Mono exec(StepContext stepContext, StepContextPosition stepCtxPos, - BiFunction f) { - ONode ctxNode1 = ComponentExecutor.toONode(stepContext); - CircleItem nextItem = this.next(ctxNode1); - if (nextItem != null) { - return Mono.just(new CircleItemResult(ctxNode1, nextItem, null)).expand(circleItemResult -> { - // put nextItem to step context - CircleItem cItem = circleItemResult.nextItem; - if (stepCtxPos.getRequestName() != null) { - stepContext.setRequestCircleItem(stepCtxPos.getStepName(), stepCtxPos.getRequestName(), - cItem.getItem(), cItem.getIndex()); - } else { - stepContext.setStepCircleItem(stepCtxPos.getStepName(), cItem.getItem(), cItem.getIndex()); - } - ONode ctxNode = circleItemResult.ctxNode; - PathMapping.setByPath(ctxNode, stepCtxPos.getPath() + ".item", cItem.getItem(), true); - PathMapping.setByPath(ctxNode, stepCtxPos.getPath() + ".index", cItem.getIndex(), true); - - if (!this.canExec(cItem.getIndex(), ctxNode, stepContext, stepCtxPos)) { - CircleItem nextItem2 = this.next(ctxNode); - if (nextItem2 == null) { - return Mono.empty(); - } - return Mono.just(new CircleItemResult(ctxNode, nextItem2, null)); - } - return f.apply(stepContext, stepCtxPos).flatMap(r -> { - if (stepCtxPos.getRequestName() != null) { - stepContext.addRequestCircleResult(stepCtxPos.getStepName(), stepCtxPos.getRequestName()); - } else { - stepContext.addStepCircleResult(stepCtxPos.getStepName()); - } - ONode ctxNode2 = ComponentExecutor.toONode(stepContext); - if (this.breakCircle(cItem.getIndex(), ctxNode2, stepContext, stepCtxPos)) { - return Mono.empty(); - } - CircleItem nextItem2 = this.next(ctxNode2); - if (nextItem2 == null) { - return Mono.empty(); - } - return Mono.just(new CircleItemResult(ctxNode2, nextItem2, r)); - }); - }).flatMap(circleItemResult -> Flux.just(circleItemResult)).collectList().flatMap(r -> { - List list = (List) r; - if (list != null && list.size() > 0) { - Collections.reverse(list); - for (int i = 0; i < list.size(); i++) { - if (list.get(i).result != null) { - return Mono.just(list.get(i).result); - } - } - } - return Mono.just(new ComponentResult()); - }); - } else { - return Mono.just(new ComponentResult()); - } - } - - @Data - class CircleItemResult { - private ONode ctxNode; - private CircleItem nextItem; - private Object result; - - public CircleItemResult(ONode ctxNode, CircleItem nextItem, Object result) { - this.ctxNode = ctxNode; - this.nextItem = nextItem; - this.result = result; - } - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/circle/CircleItem.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/circle/CircleItem.java deleted file mode 100644 index a1d25c4..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/circle/CircleItem.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.component.circle; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * - * @author Francis Dong - * - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class CircleItem { - - private Object item; - - private Integer index; - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/condition/Condition.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/condition/Condition.java deleted file mode 100644 index e09ceb1..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/condition/Condition.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.component.condition; - -import java.util.Collection; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; -import org.springframework.util.ObjectUtils; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.fizz.component.ComponentTypeEnum; -import com.fizzgate.fizz.component.IComponent; -import com.fizzgate.fizz.component.OperatorEnum; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.field.RefDataTypeEnum; -import com.fizzgate.fizz.field.ValueTypeEnum; -import com.fizzgate.fizz.input.PathMapping; -import com.fizzgate.fizz.input.extension.request.RequestInput; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Condition component - * - * @author Francis Dong - * - */ -@Data -public class Condition implements IComponent { - - private static final Logger LOGGER = LoggerFactory.getLogger(Condition.class); - - private static final String type = ComponentTypeEnum.CONDITION.getCode(); - - private String desc; - - private ConditionValue value1; - - private OperatorEnum operator; - - private ConditionValue value2; - - public Condition(String desc, ConditionValue value1, OperatorEnum operator, ConditionValue value2) { - this.desc = desc; - this.value1 = value1; - this.operator = operator; - this.value2 = value2; - } - - @Override - public ComponentTypeEnum getType() { - return ComponentTypeEnum.getEnumByCode(type); - } - - /** - * Execute condition - * - * @return - */ - @SuppressWarnings({ "rawtypes" }) - public boolean exec(ONode ctxNode) { - if (value1 == null || operator == null) { - return false; - } - - boolean rs = false; - try { - Object v1 = null; - if (ValueTypeEnum.FIXED.equals(value1.getType())) { - v1 = value1.getValue(); - } else { - v1 = PathMapping.getValueByPath(ctxNode, (String) value1.getValue()); - v1 = this.cast(value1.getRefDataType(), v1); - } - - Object v2 = null; - if (value2 != null && value2.getType() != null) { - if (ValueTypeEnum.FIXED.equals(value2.getType())) { - v2 = value2.getValue(); - } else { - v2 = PathMapping.getValueByPath(ctxNode, (String) value2.getValue()); - v2 = this.cast(value2.getRefDataType(), v2); - } - } - - switch (operator) { - case EQ: - if (v1 == null && v2 == null) { - rs = true; - } else if (v1 != null && v2 != null) { - rs = this.compare(v1, v2) == 0; - } - break; - case NE: - if (v1 == null && v2 == null) { - rs = false; - } else if ((v1 == null && v2 != null) || (v1 != null && v2 == null)) { - rs = true; - } else if (v1 != null && v2 != null) { - rs = this.compare(v1, v2) != 0; - } - break; - case GT: - rs = this.compare(v1, v2) > 0; - break; - case GE: - rs = this.compare(v1, v2) >= 0; - break; - case LT: - rs = this.compare(v1, v2) < 0; - break; - case LE: - rs = this.compare(v1, v2) <= 0; - break; - case CONTAINS: - if (v1 == null) { - rs = false; - break; - } - if (v1 instanceof Collection && !(v2 instanceof Collection)) { - Collection coll1 = (Collection) v1; - if (v2 instanceof Integer || v2 instanceof Long) { - Long el = Long.valueOf(v2.toString()); - rs = containsLong(coll1, el); - } else if (v2 instanceof Float || v2 instanceof Double) { - Double el = Double.valueOf(v2.toString()); - rs = containsDouble(coll1, el); - } else { - rs = CollectionUtils.contains(coll1.iterator(), v2); - } - } else if (!(v1 instanceof Collection)) { - throw new FizzRuntimeException("value1 must be a collection"); - } else if (v2 instanceof Collection) { - throw new FizzRuntimeException("value2 can not be a collection"); - } - break; - case NOTCONTAIN: - if (v1 == null) { - rs = true; - break; - } - if (v1 instanceof Collection && !(v2 instanceof Collection)) { - Collection coll1 = (Collection) v1; - if (v2 instanceof Integer || v2 instanceof Long) { - Long el = Long.valueOf(v2.toString()); - rs = !containsLong(coll1, el); - } else if (v2 instanceof Float || v2 instanceof Double) { - Double el = Double.valueOf(v2.toString()); - rs = !containsDouble(coll1, el); - } else { - rs = !CollectionUtils.contains(coll1.iterator(), v2); - } - } else if (!(v1 instanceof Collection)) { - throw new FizzRuntimeException("value1 must be a collection"); - } else if (v2 instanceof Collection) { - throw new FizzRuntimeException("value2 can not be a collection"); - } - break; - case CONTAINSANY: - if (v1 == null || v2 == null) { - rs = false; - break; - } - if (v1 instanceof Collection && v2 instanceof Collection) { - Collection coll1 = (Collection) v1; - Collection coll2 = (Collection) v2; - rs = CollectionUtils.containsAny(coll1, coll2); - } else if (!(v1 instanceof Collection)) { - throw new FizzRuntimeException("value1 must be a collection"); - } else if (!(v2 instanceof Collection)) { - throw new FizzRuntimeException("value2 must be a collection"); - } - break; - case ISNULL: - rs = v1 == null; - break; - case ISNOTNULL: - rs = v1 != null; - break; - case ISBLANK: - rs = v1 == null || StringUtils.isBlank(v1.toString()); - break; - case ISNOTBLANK: - rs = v1 != null && StringUtils.isNotBlank(v1.toString()); - break; - case ISEMPTY: - rs = v1 == null || (v1 instanceof Collection && ((Collection) v1).isEmpty()) - || (v1 instanceof Map && ((Map) v1).isEmpty()); - break; - case ISNOTEMPTY: - if (v1 != null) { - if (v1 instanceof Collection) { - rs = !((Collection) v1).isEmpty(); - } else if (v1 instanceof Map) { - rs = !((Map) v1).isEmpty(); - } - } - break; - default: - break; - } - } catch (FizzRuntimeException e) { - String message = type + ": " + e.getMessage() + ", data=" + JSON.toJSONString(this); - LOGGER.error(message, e); - throw new FizzRuntimeException(message, e.getCause()); - } - - return rs; - } - - @SuppressWarnings("rawtypes") - private int compare(Object v1, Object v2) { - if (v1 == null || v2 == null) { - throw new FizzRuntimeException("value1 and value2 can not be null"); - } - if (v1 instanceof Boolean && v2 instanceof Boolean) { - Boolean n1 = (Boolean) v1; - Boolean n2 = (Boolean) v2; - return n1.compareTo(n2); - } else if ((v1 instanceof Integer || v1 instanceof Long || v1 instanceof Float || v1 instanceof Double) - && (v2 instanceof Integer || v2 instanceof Long || v2 instanceof Float || v2 instanceof Double)) { - // compare value if both are numbers - Double n1 = Double.valueOf(v1.toString()); - Double n2 = Double.valueOf(v2.toString()); - return n1.compareTo(n2); - } else if (v1 instanceof String && v2 instanceof String) { - String s1 = v1.toString(); - String s2 = v2.toString(); - return s1.compareTo(s2); - } else { - throw new FizzRuntimeException( - "types of value1 and value2 are not consistent or not supported for comparision"); - } - } - - private Object cast(RefDataTypeEnum type, Object val) { - if (type != null && val != null) { - switch (type) { - case INT: - val = Integer.valueOf(val.toString()); - break; - case LONG: - val = Long.valueOf(val.toString()); - break; - case FLOAT: - val = Float.valueOf(val.toString()); - break; - case DOUBLE: - val = Double.valueOf(val.toString()); - break; - case BOOLEAN: - val = Boolean.valueOf(val.toString()); - break; - case STRING: - val = val.toString(); - break; - } - } - return val; - } - - @SuppressWarnings("rawtypes") - private boolean containsLong(Collection coll, Long el) { - if (CollectionUtils.isEmpty(coll)) { - return false; - } - - for (Object obj : coll) { - Long obj2 = null; - if (obj instanceof Integer) { - obj2 = Long.valueOf(obj.toString()); - } - if (ObjectUtils.nullSafeEquals(obj2, el)) { - return true; - } - } - - return false; - } - - @SuppressWarnings("rawtypes") - private boolean containsDouble(Collection coll, Double el) { - if (CollectionUtils.isEmpty(coll)) { - return false; - } - - for (Object obj : coll) { - Double obj2 = null; - if (obj instanceof Float) { - obj2 = Double.valueOf(obj.toString()); - } - if (ObjectUtils.nullSafeEquals(obj2, el)) { - return true; - } - } - - return false; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/component/condition/ConditionValue.java b/fizz-core/src/main/java/com/fizzgate/fizz/component/condition/ConditionValue.java deleted file mode 100644 index 5414547..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/component/condition/ConditionValue.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.component.condition; - -import com.fizzgate.fizz.field.FixedDataTypeEnum; -import com.fizzgate.fizz.field.RefDataTypeEnum; -import com.fizzgate.fizz.field.ValueTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Condition value - * - * @author Francis Dong - * - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ConditionValue { - - private ValueTypeEnum type; - - private FixedDataTypeEnum fixedDataType; - - private RefDataTypeEnum refDataType; - - private Object value; - - public ConditionValue(ValueTypeEnum type, FixedDataTypeEnum fixedDataType, Object value) { - this.type = type; - this.fixedDataType = fixedDataType; - this.value = value; - } - - public ConditionValue(ValueTypeEnum type, RefDataTypeEnum refDataType, Object value) { - this.type = type; - this.refDataType = refDataType; - this.value = value; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/exception/FizzRuntimeException.java b/fizz-core/src/main/java/com/fizzgate/fizz/exception/FizzRuntimeException.java deleted file mode 100644 index c3e5138..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/exception/FizzRuntimeException.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.exception; - -import com.fizzgate.fizz.StepContext; - -public class FizzRuntimeException extends RuntimeException { - - private StepContext stepContext; - - public FizzRuntimeException(String message) { - super(message); - } - - public FizzRuntimeException(String message, Throwable cause) { - super(message, cause); - this.setStackTrace(cause.getStackTrace()); - } - - public FizzRuntimeException(String message, StepContext stepContext) { - super(message); - this.stepContext = stepContext; - } - - public FizzRuntimeException(String message, Throwable cause, StepContext stepContext) { - super(message, cause); - this.setStackTrace(cause.getStackTrace()); - this.stepContext = stepContext; - } - - public StepContext getStepContext() { - return stepContext; - } - - public void setStepContext(StepContext stepContext) { - this.stepContext = stepContext; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/field/FieldConfig.java b/fizz-core/src/main/java/com/fizzgate/fizz/field/FieldConfig.java deleted file mode 100644 index b1b736d..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/field/FieldConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.field; - -import java.util.Map; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * - * @author Francis Dong - * - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -public class FieldConfig { - - private ValueTypeEnum type; - - private FixedDataTypeEnum fixedDataType; - - private RefDataTypeEnum refDataType; - - private Object value; - - public FieldConfig(Map configMap) { - if (configMap != null && !configMap.isEmpty()) { - if (configMap.containsKey("type")) { - this.type = ValueTypeEnum.getEnumByCode(configMap.get("type").toString()); - } - if (configMap.containsKey("fixedDataType")) { - this.fixedDataType = FixedDataTypeEnum.getEnumByCode(configMap.get("fixedDataType").toString()); - } - if (configMap.containsKey("refDataType")) { - this.refDataType = RefDataTypeEnum.getEnumByCode(configMap.get("refDataType").toString()); - } - if (configMap.containsKey("value")) { - this.value = configMap.get("value"); - } - } - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/field/FixedDataTypeEnum.java b/fizz-core/src/main/java/com/fizzgate/fizz/field/FixedDataTypeEnum.java deleted file mode 100644 index 4a3b10e..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/field/FixedDataTypeEnum.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.field; - -/** - * Data type of fixed value - * - * @author Francis Dong - * - */ -public enum FixedDataTypeEnum{ - - NUMBER("number"), STRING("string"), BOOLEAN("boolean"); - - private String code; - - private FixedDataTypeEnum(String code) { - this.code = code; - } - - public String getCode() { - return this.code; - } - - public void setCode(String code) { - this.code = code; - } - - public static FixedDataTypeEnum getEnumByCode(String code) { - for (FixedDataTypeEnum e : FixedDataTypeEnum.values()) { - if (e.getCode().equals(code)) { - return e; - } - } - return null; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/field/RefDataTypeEnum.java b/fizz-core/src/main/java/com/fizzgate/fizz/field/RefDataTypeEnum.java deleted file mode 100644 index ca926a3..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/field/RefDataTypeEnum.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.field; - -/** - * Data type of reference value - * - * @author Francis Dong - * - */ -public enum RefDataTypeEnum { - - INT("int"), LONG("long"), FLOAT("float"), DOUBLE("double"), STRING("string"), BOOLEAN("boolean"), ARRAY("array"); - - private String code; - - private RefDataTypeEnum(String code) { - this.code = code; - } - - public String getCode() { - return this.code; - } - - public void setCode(String code) { - this.code = code; - } - - public static RefDataTypeEnum getEnumByCode(String code) { - for (RefDataTypeEnum e : RefDataTypeEnum.values()) { - if (e.getCode().equals(code)) { - return e; - } - } - return null; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/field/ValueTypeEnum.java b/fizz-core/src/main/java/com/fizzgate/fizz/field/ValueTypeEnum.java deleted file mode 100644 index fab8b64..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/field/ValueTypeEnum.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.field; - -/** - * Value Type - * - * @author Francis Dong - * - */ -public enum ValueTypeEnum { - - FIXED("fixed"), REF("ref"), FUNC("func"); - - private String code; - - private ValueTypeEnum(String code) { - this.code = code; - } - - public String getCode() { - return code; - } - - public static ValueTypeEnum getEnumByCode(String code) { - for (ValueTypeEnum e : ValueTypeEnum.values()) { - if (e.getCode().equals(code)) { - return e; - } - } - return null; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/CodecFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/CodecFunc.java deleted file mode 100644 index 23553cc..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/CodecFunc.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.function; - -import java.io.UnsupportedEncodingException; -import java.security.Key; -import java.util.Base64; - -import javax.crypto.Cipher; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.digest.HmacAlgorithms; -import org.apache.commons.codec.digest.HmacUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fizzgate.util.DigestUtils; - -/** - * Codec Functions - * - * @author Francis Dong - * - */ -public class CodecFunc implements IFunc { - - private static final Logger LOGGER = LoggerFactory.getLogger(CodecFunc.class); - - private static final String CHARSET_UTF8 = "UTF-8"; - - private static final String IV = "12345678"; - - private static CodecFunc singleton; - - public static CodecFunc getInstance() { - if (singleton == null) { - synchronized (CodecFunc.class) { - if (singleton == null) { - CodecFunc instance = new CodecFunc(); - instance.init(); - singleton = instance; - } - } - } - return singleton; - } - - private CodecFunc() { - } - - public void init() { - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.md5", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha1", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha256", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha384", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.sha512", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.base64Encode", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.base64Decode", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.aesEncrypt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.aesDecrypt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.desEncrypt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.desDecrypt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.hmacMd5", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.hmacSha1", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.hmacSha224", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.hmacSha256", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.hmacSha384", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "codec.hmacSha512", this); - } - - public String md5(String data) { - return DigestUtils.md5Hex(data); - } - - public String sha1(String data) { - return DigestUtils.sha1Hex(data); - } - - public String sha256(String data) { - return DigestUtils.sha256Hex(data); - } - - public String sha384(String data) { - return DigestUtils.sha384Hex(data); - } - - public String sha512(String data) { - return DigestUtils.sha512Hex(data); - } - - public String base64Encode(String data) throws Exception { - try { - return Base64.getEncoder().encodeToString(data.getBytes(CHARSET_UTF8)); - } catch (UnsupportedEncodingException e) { - LOGGER.error("Base64 encode error, data={}", data, e); - throw e; - } - } - - public String base64Decode(String data) throws Exception { - return new String(Base64.getDecoder().decode(data)); - } - - public String aesEncrypt(String data, String key) throws Exception { - if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { - return null; - } - try { - Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); - SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET_UTF8), "AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); - byte[] result = cipher.doFinal(data.getBytes(CHARSET_UTF8)); - return Base64.getEncoder().encodeToString(result); - } catch (Exception e) { - LOGGER.error("AES encrypt error, data={}", data, e); - throw e; - } - } - - public String aesDecrypt(String data, String key) throws Exception { - if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { - return null; - } - try { - Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); - SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(CHARSET_UTF8), "AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); - byte[] result = Base64.getDecoder().decode(data); - return new String(cipher.doFinal(result)); - } catch (Exception e) { - LOGGER.error("AES decrypt error, data={}", data, e); - throw e; - } - } - - public String desEncrypt(String data, String key) throws Exception { - if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { - return null; - } - try { - DESKeySpec dks = new DESKeySpec(key.getBytes(CHARSET_UTF8)); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - Key secretKey = keyFactory.generateSecret(dks); - Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); - IvParameterSpec iv = new IvParameterSpec(IV.getBytes(CHARSET_UTF8)); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); - byte[] bytes = cipher.doFinal(data.getBytes(CHARSET_UTF8)); - return new String(Base64.getEncoder().encode(bytes)); - } catch (Exception e) { - LOGGER.error("DES eecrypt error, data={}", data, e); - throw e; - } - } - - public String desDecrypt(String data, String key) throws Exception { - if (StringUtils.isBlank(data) || StringUtils.isBlank(key)) { - return null; - } - try { - DESKeySpec dks = new DESKeySpec(key.getBytes(CHARSET_UTF8)); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - Key secretKey = keyFactory.generateSecret(dks); - Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); - IvParameterSpec iv = new IvParameterSpec(IV.getBytes(CHARSET_UTF8)); - cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); - return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes(CHARSET_UTF8))), CHARSET_UTF8); - } catch (Exception e) { - LOGGER.error("DES decrypt error, data={}", data, e); - throw e; - } - } - - public String hmacMd5(String data, String secretKey) { - return new HmacUtils(HmacAlgorithms.HMAC_MD5, secretKey).hmacHex(data); - } - - public String hmacSha1(String data, String secretKey) { - return new HmacUtils(HmacAlgorithms.HMAC_SHA_1, secretKey).hmacHex(data); - } - - public String hmacSha224(String data, String secretKey) { - return new HmacUtils(HmacAlgorithms.HMAC_SHA_224, secretKey).hmacHex(data); - } - - public String hmacSha256(String data, String secretKey) { - return new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secretKey).hmacHex(data); - } - - public String hmacSha384(String data, String secretKey) { - return new HmacUtils(HmacAlgorithms.HMAC_SHA_384, secretKey).hmacHex(data); - } - - public String hmacSha512(String data, String secretKey) { - return new HmacUtils(HmacAlgorithms.HMAC_SHA_512, secretKey).hmacHex(data); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/CommonFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/CommonFunc.java deleted file mode 100644 index 9b59ffc..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/CommonFunc.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.function; - -import java.lang.reflect.Array; -import java.util.Collection; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Common Functions - * - * @author Francis Dong - * - */ -public class CommonFunc implements IFunc { - - private static final Logger LOGGER = LoggerFactory.getLogger(CommonFunc.class); - - private static CommonFunc singleton; - - public static CommonFunc getInstance() { - if (singleton == null) { - synchronized (CommonFunc.class) { - if (singleton == null) { - CommonFunc instance = new CommonFunc(); - instance.init(); - singleton = instance; - } - } - } - return singleton; - } - - private CommonFunc() { - } - - public void init() { - FuncExecutor.register(NAME_SPACE_PREFIX + "common.iif", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.equals", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.isNull", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.isNotNull", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.isBlank", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.isNotBlank", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.isEmpty", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.isNotEmpty", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.and", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.or", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "common.not", this); - } - - /** - * Immediate if function (iif) - * - * @param exprResult the result of expression that is to be evaluated. - * @param truepart defines what the iif function returns if the exprResult is - * true. - * @param falsepart defines what the iif function returns if the exprResult is - * false. - * @return returns the truepart or falsepart based on the value of the - * exprResult - */ - public Object iif(boolean exprResult, Object truepart, Object falsepart) { - return exprResult ? truepart : falsepart; - } - - /** - * - * @param obj1 - * @param obj2 - * @return - */ - public boolean equals(Object obj1, Object obj2) { - if (obj1 == null) { - if (obj2 == null) { - return true; - } - return false; - } - return obj1.equals(obj2); - } - - public boolean isNull(Object obj) { - return null == obj; - } - - public boolean isNotNull(Object obj) { - return null != obj; - } - - public boolean isBlank(String obj) { - return StringUtils.isBlank(obj); - } - - public boolean isNotBlank(String obj) { - return StringUtils.isNotBlank(obj); - } - - @SuppressWarnings("rawtypes") - public boolean isEmpty(Object obj) { - if (obj == null) { - return true; - } - if (obj instanceof Collection) { - return ((Collection) obj).isEmpty(); - } else if (obj instanceof Map) { - return ((Map) obj).isEmpty(); - } else if (obj.getClass().isArray()) { - return Array.getLength(obj) == 0; - } else if (obj instanceof CharSequence) { - return ((CharSequence) obj).length() == 0; - } - return false; - } - - public boolean isNotEmpty(Object obj) { - return !isEmpty(obj); - } - - /** - * Return true if all args are true
- * - * @param objs - * @return - */ - public boolean and(Boolean... objs) { - if (objs != null && objs.length > 0) { - for (int i = 0; i < objs.length; i++) { - if (objs[i] == null || !objs[i]) { - return false; - } - } - return true; - } - return false; - } - - /** - * Return true if any arg is true
- * - * @param objs - * @return - */ - public boolean or(Boolean... objs) { - if (objs != null && objs.length > 0) { - for (int i = 0; i < objs.length; i++) { - if (objs[i] != null && objs[i]) { - return true; - } - } - } - return false; - } - - public boolean not(Boolean obj) { - return !(obj == null ? false : obj); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/DateFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/DateFunc.java deleted file mode 100644 index 86b4266..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/DateFunc.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.function; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fizzgate.fizz.exception.FizzRuntimeException; - -/** - * Date Functions - * - * @author Francis Dong - * - */ -public class DateFunc implements IFunc { - - private static final Logger LOGGER = LoggerFactory.getLogger(DateFunc.class); - - public static final String DEFAULT_TIMEZONE = "GMT+08:00"; - - private static DateFunc singleton; - - public static DateFunc getInstance() { - if (singleton == null) { - synchronized (DateFunc.class) { - if (singleton == null) { - DateFunc instance = new DateFunc(); - instance.init(); - singleton = instance; - } - } - } - return singleton; - } - - private DateFunc() { - } - - public void init() { - FuncExecutor.register(NAME_SPACE_PREFIX + "date.timestamp", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "date.getTime", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "date.now", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "date.add", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "date.formatTs", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "date.changePattern", this); - } - - /** - * Date pattern
- * yyyy-MM-dd - */ - public final static String DATE_FORMAT = "yyyy-MM-dd"; - - /** - * Time pattren
- * HH:mm:ss - */ - public final static String TIME_FORMAT = "HH:mm:ss"; - - /** - * Short time pattren
- * HH:mm - */ - public final static String SHORT_TIME_FORMAT = "HH:mm"; - - /** - * Date time pattern
- * yyyy-MM-dd HH:mm:ss - */ - public final static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; - - /** - * Returns current timestamp (Milliseconds) - * - * @return - */ - public long timestamp() { - return System.currentTimeMillis(); - } - - /** - * Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT - * represented by this Date object. - * - * @param date date string - * @param pattern Date time pattern - * @param timeZone [optional] timeZone - * @return - */ - public Long getTime(String date, String pattern, String... timeZone) { - if (StringUtils.isBlank(date)) { - return null; - } - Date d = parse(date, pattern, timeZone); - return d.getTime(); - } - - /** - * Returns current time with the given pattern
- * Frequently-used pattern:
- * yyyy-MM-dd HH:mm:ss
- * yyyy-MM-dd
- * HH:mm:ss
- * HH:mm
- * yyyy-MM-dd HH:mm:ss Z
- * - * @param pattern the pattern describing the date and time format, dafault - * yyyy-MM-dd HH:mm:ss - * @param timeZone [optional] timeZone - * @return - */ - public String now(String pattern, String... timeZone) { - return formatDate(new Date(), pattern, timeZone); - } - - /** - * Adds or subtracts the specified amount of time to the given calendar field, - * based on the calendar's rules. For example, to subtract 5 hours from the - * current time of the calendar, you can achieve it by calling: - *

- * add("2021-08-04 14:23:12", "yyyy-MM-dd HH:mm:ss", 4, -5). - * - * @param date date string - * @param pattern date pattern of the given date string - * @param field the calendar field,
- * 1 for millisecond
- * 2 for second
- * 3 for minute
- * 4 for hour
- * 5 for date
- * 6 for month
- * 7 for year
- * @param amount the amount of date or time to be added to the field - * @param timeZone [optional] timeZone - * @return - */ - public String add(String date, String pattern, int field, int amount, String... timeZone) { - if (StringUtils.isBlank(date)) { - return null; - } - Date d = parse(date, pattern, timeZone); - if (d != null) { - // convert to calendar field - int calField = 0; - switch (field) { - case 1: - calField = Calendar.MILLISECOND; - break; - case 2: - calField = Calendar.SECOND; - break; - case 3: - calField = Calendar.MINUTE; - break; - case 4: - calField = Calendar.HOUR; - break; - case 5: - calField = Calendar.DATE; - break; - case 6: - calField = Calendar.MONTH; - break; - case 7: - calField = Calendar.YEAR; - break; - default: - LOGGER.error("invalid field, date={} pattern={} filed={}", date, pattern, field); - throw new FizzRuntimeException( - "invalid field, date=" + date + "pattern=" + pattern + " filed=" + field); - } - return formatDate(addToFiled(d, calField, amount), pattern, timeZone); - } - return null; - } - - /** - * Format the a timestamp to the given pattern - * - * @param timestamp - * @param pattern - * @param timeZone [optional] timeZone - * @return - */ - public String formatTs(Long timestamp, String pattern, String... timeZone) { - if (timestamp == null) { - return null; - } - return formatDate(new Date(timestamp), pattern, timeZone); - } - - /** - * Format the a time with source pattern to the target pattern - * - * @param dateStr date - * @param sourcePattern source pattern - * @param targetPattern target pattern - * @param timeZone [optional] timeZone - * @return - */ - public String changePattern(String dateStr, String sourcePattern, String targetPattern, String... timeZone) { - if (StringUtils.isBlank(dateStr)) { - return null; - } - return formatDate(parse(dateStr, sourcePattern, timeZone), targetPattern, timeZone); - } - - /** - * Adds or subtracts the specified amount of time to the given calendar field - * - * @param date a Date - * @param field field that the times to be add to, such as: Calendar.SECOND, - * Calendar.YEAR - * @param amount the amount of date or time to be added to the field - * @return - */ - private Date addToFiled(Date date, int field, int amount) { - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - cal.add(field, amount); - return cal.getTime(); - } - - /** - * Parse string to Date - * - * @param dateStr String to be parsed - * @param pattern pattern of dateStr - * @param timeZone [optional] timeZone - * @return - */ - private Date parse(String dateStr, String pattern, String... timeZone) { - if (StringUtils.isBlank(dateStr)) { - return null; - } - SimpleDateFormat sdf = new SimpleDateFormat(pattern == null ? DATE_TIME_FORMAT : pattern); - if (timeZone != null && timeZone.length > 0) { - sdf.setTimeZone(TimeZone.getTimeZone(timeZone[0])); - } else { - sdf.setTimeZone(TimeZone.getTimeZone(DEFAULT_TIMEZONE)); - } - try { - return sdf.parse(dateStr); - } catch (ParseException e) { - LOGGER.error("Parse date error, dateStr={} pattern={}", dateStr, pattern, e); - throw new FizzRuntimeException("Parse date error, dateStr=" + dateStr + " pattern=" + pattern, e); - } - } - - /** - * Format date with the given pattern
- * Frequently-used pattern:
- * yyyy-MM-dd HH:mm:ss
- * yyyy-MM-dd
- * HH:mm:ss
- * HH:mm
- * yyyy-MM-dd HH:mm:ss Z
- * - * @param pattern [optional] the pattern describing the date and time format, - * dafault yyyy-MM-dd HH:mm:ss - * @param timeZone [optional] timeZone - * @return - */ - private String formatDate(Date date, String pattern, String... timeZone) { - if (date == null) { - return null; - } - SimpleDateFormat sdf = new SimpleDateFormat(pattern == null ? DATE_TIME_FORMAT : pattern); - if (timeZone != null && timeZone.length > 0) { - sdf.setTimeZone(TimeZone.getTimeZone(timeZone[0])); - } else { - sdf.setTimeZone(TimeZone.getTimeZone(DEFAULT_TIMEZONE)); - } - return sdf.format(date); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/FuncExecutor.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/FuncExecutor.java deleted file mode 100644 index 4292196..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/FuncExecutor.java +++ /dev/null @@ -1,535 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.beanutils.ConvertUtils; -import org.apache.commons.lang3.StringUtils; -import org.noear.snack.ONode; -import org.reflections.Reflections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.input.Input; -import com.fizzgate.fizz.input.PathMapping; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * Function Register - * - * @author Francis Dong - * - */ -public class FuncExecutor { - - private static final Logger LOGGER = LoggerFactory.getLogger(FuncExecutor.class); - - private static final Map funcMap = new HashMap<>(); - - private static Pattern NUMBER_PATTERN = Pattern - .compile("^[-\\+]?[\\d]+\\s*[,\\)]{1}|^[-\\+]?[\\d]+\\.[\\d]+\\s*[,\\)]{1}"); - - private static Pattern FLOAT_PATTERN = Pattern.compile("^[-\\+]?[\\d]+\\.[\\d]+\\s*[,\\)]{1}"); - - private static FuncExecutor singleton; - - public static FuncExecutor getInstance() { - if (singleton == null) { - synchronized (FuncExecutor.class) { - if (singleton == null) { - singleton = new FuncExecutor(); - init(); - } - } - } - return singleton; - } - - private FuncExecutor() { - } - - public static void init() { - try { - Reflections reflections = new Reflections("com.fizzgate.fizz.function"); - Set> types = reflections.getSubTypesOf(IFunc.class); - for (Class fnType : types) { - Method method = fnType.getMethod("getInstance"); - method.invoke(fnType); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Register a function instance - * - * @param namespace a name to identify the given function instance - * @param func - */ - public static void register(String namespace, IFunc funcInstance) { - if (StringUtils.isBlank(namespace)) { - LOGGER.warn("namespace is required"); - return; - } - if (!namespace.startsWith(IFunc.NAME_SPACE_PREFIX)) { - LOGGER.warn("namespace must start with fn."); - return; - } - if (funcInstance == null) { - LOGGER.warn("function instance is required"); - return; - } - funcMap.put(namespace, funcInstance); - } - - /** - * Execute function - * - * @param funcExpression - * @return - */ - public Object exec(ONode ctxNode, String funcExpression) { - RecursionContext ctx = new RecursionContext(); - ctx.setFuncExpression(funcExpression); - return doExec(ctxNode, ctx); - } - - private Object doExec(ONode ctxNode, RecursionContext ctx) { - String funcExpression = ctx.funcExpression; - if (StringUtils.isBlank(funcExpression)) { - return null; - } - funcExpression = StringUtils.trim(funcExpression); - int pos1 = funcExpression.indexOf("("); - if (pos1 == -1) { - LOGGER.warn("func expression is invalid, expression: {}", funcExpression); - return null; - } - if (!funcExpression.endsWith(")")) { - LOGGER.warn("func expression is invalid, expression: {}", funcExpression); - return null; - } - - String path = funcExpression.substring(0, pos1); - int lastDotPos = path.lastIndexOf("."); - if (pos1 == -1) { - LOGGER.warn("func expression is invalid, expression: {}", funcExpression); - return null; - } - String namespace = path.substring(0, lastDotPos); - String methodName = path.substring(lastDotPos + 1); - - Object funcInstance = funcMap.get(path); - if (funcInstance == null) { - String msg = String.format("function not found: %s, expression: %s", path, funcExpression); - LOGGER.warn(msg); - throw new FizzRuntimeException(msg); - } - - try { - Method method = findMethod(funcInstance.getClass(), methodName); - Class[] paramTypes = method.getParameterTypes(); - ctx.funcExpression = funcExpression; - Object[] args = parseArgs(ctxNode, ctx, funcExpression, paramTypes, method.isVarArgs()); - if (args == null) { - return method.invoke(funcInstance); - } - return method.invoke(funcInstance, args); - } catch (FizzRuntimeException e) { - throw e; - } catch (InvocationTargetException e) { - Throwable targetEx = e.getTargetException(); - if (targetEx instanceof FizzRuntimeException) { - throw (FizzRuntimeException) targetEx; - } - String msg = targetEx.getMessage(); - if (msg == null) { - msg = String.format("execute function error: %s", funcExpression); - } - LOGGER.error(msg, targetEx); - throw new FizzRuntimeException(msg, targetEx); - } catch (Exception e) { - String msg = String.format("execute function error: %s", funcExpression); - LOGGER.error(msg, e); - throw new FizzRuntimeException(msg, e); - } - } - - private Method findMethod(Class funcClass, String methodName) { - Method[] methods = funcClass.getDeclaredMethods(); - for (Method method : methods) { - if (method.getName().equals(methodName)) { - return method; - } - } - String msg = String.format("method not found: %s, class: %s", methodName, funcClass); - LOGGER.warn(msg); - throw new FizzRuntimeException(msg); - } - - /** - * funcExpression sample:
- * fn.date.add({step1.request1.response.body.date}, "yyyy-MM-dd HH:mm:ss", 1, - * 1000)
- * fn.date.add(true, fn.date.add({step1.request1.response.body.date}, - * "yyyy-MM-dd HH:mm:ss", 1, 1000), "yyyy-MM-dd HH:mm:ss\"))}}", 1, 1000)
- * - * @param funcExpression - * @param paramTypes - * @return - */ - private Object[] parseArgs(ONode ctxNode, RecursionContext ctx, String funcExpression, Class[] paramTypes, - boolean isVarArgs) { - int pos1 = funcExpression.indexOf("("); - // int pos2 = funcExpression.lastIndexOf(")"); - String argsStr = funcExpression.substring(pos1 + 1); - argsStr = StringUtils.trim(argsStr); - - Object[] args = new Object[paramTypes.length]; - if (paramTypes.length == 0) { - // no argument method - if (hasCloseParenthesis(argsStr, 0)) { - ctx.funcExpression = argsStr.substring(1); - } else { - ctx.funcExpression = argsStr; - } - return args; - } - // check if there is any argument - if (StringUtils.isBlank(argsStr)) { - if (paramTypes == null || paramTypes.length == 0) { - ctx.funcExpression = argsStr; - return args; - } else if (paramTypes.length == 1 && isVarArgs) { - // check if variable arguments - ctx.funcExpression = argsStr; - return args; - } else { - throw new FizzRuntimeException( - String.format("missing argument, Function Expression: %s", funcExpression)); - } - } else if (hasCloseParenthesis(argsStr, 0)) { - if (paramTypes == null || paramTypes.length == 0) { - ctx.funcExpression = argsStr.substring(1); - return args; - } else if (paramTypes.length == 1 && isVarArgs) { - ctx.funcExpression = argsStr.substring(1); - return args; - } else { - throw new FizzRuntimeException( - String.format("missing argument, Function Expression: %s", funcExpression)); - } - } - - List varArgs = new ArrayList<>(); - for (int i = 0; i < paramTypes.length; i++) { - Class clazz = paramTypes[i]; - if (StringUtils.isBlank(argsStr)) { - if (isVarArgs && i == paramTypes.length - 1 && args[i] == null) { - args[i] = Array.newInstance(clazz.getComponentType(), 0); - } - break; - } - ArgsStrContainer argsStrContainer = new ArgsStrContainer(argsStr, i); - if (argsStr.startsWith("\"")) { // string - int pos = findStringEngPos(argsStr); - if (pos != -1) { - String arg = argsStr.substring(1, pos); - if (isVarArgs && i == paramTypes.length - 1) { - varArgs.add(arg); - args[i] = varArgs.toArray(new String[varArgs.size()]); - } else { - args[i] = arg; - } - argsStrContainer = this.trimArgStr(argsStrContainer, pos + 1, isVarArgs, paramTypes.length, - funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } else { - throw new FizzRuntimeException( - String.format("invalid argument: %s, Function Expression: %s", argsStr, funcExpression)); - } - } else if (argsStr.matches("^null\\s*,.*") || argsStr.matches("^null\\s*\\).*")) { // null - if (isVarArgs && i == paramTypes.length - 1) { - varArgs.add(null); - Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); - for (int j = 0; j < varArgs.size(); j++) { - Array.set(arr, j, varArgs.get(j)); - } - args[i] = arr; - } else { - args[i] = null; - } - argsStrContainer = this.trimArgStr(argsStrContainer, 4, isVarArgs, paramTypes.length, funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } else if (argsStr.matches("^true\\s*,.*") || argsStr.matches("^true\\s*\\).*")) { // boolean - if (isVarArgs && i == paramTypes.length - 1) { - varArgs.add(true); - args[i] = varArgs.toArray(new Boolean[varArgs.size()]); - } else { - args[i] = true; - } - argsStrContainer = this.trimArgStr(argsStrContainer, 4, isVarArgs, paramTypes.length, funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } else if (argsStr.matches("^false\\s*,.*") || argsStr.matches("^false\\s*\\).*")) { // boolean - if (isVarArgs && i == paramTypes.length - 1) { - varArgs.add(false); - args[i] = varArgs.toArray(new Boolean[varArgs.size()]); - } else { - args[i] = false; - } - argsStrContainer = this.trimArgStr(argsStrContainer, 5, isVarArgs, paramTypes.length, funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } else if (argsStr.startsWith("{")) { // reference value - int pos = argsStr.indexOf("}", 1); - if (pos != -1) { - String refKey = argsStr.substring(1, pos); - Object arg = PathMapping.getValueByPath(ctxNode, refKey); - if (isVarArgs && i == paramTypes.length - 1) { - arg = ConvertUtils.convert(arg, clazz.getComponentType()); - varArgs.add(arg); - Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); - for (int j = 0; j < varArgs.size(); j++) { - Array.set(arr, j, varArgs.get(j)); - } - args[i] = arr; - - } else { - arg = ConvertUtils.convert(arg, clazz); - args[i] = arg; - } - - argsStrContainer = this.trimArgStr(argsStrContainer, pos + 1, isVarArgs, paramTypes.length, - funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } else { - throw new FizzRuntimeException( - String.format("invalid argument: %s, Function Expression: %s", argsStr, funcExpression)); - } - } else { - Matcher m = NUMBER_PATTERN.matcher(argsStr); - boolean isNumber = m.find(); - if (isNumber) { - int pos = m.end(); - String matchedStr = m.group(); - // Number - String strNum = StringUtils.trim(matchedStr.substring(0, pos - 1)); - if (isVarArgs && i == paramTypes.length - 1) { - Object arg = null; - if (clazz.getComponentType().equals(Object.class)) { - if (FLOAT_PATTERN.matcher(argsStr).find()) { - arg = ConvertUtils.convert(strNum, Double.class); - } else { - arg = ConvertUtils.convert(strNum, Long.class); - } - } else { - arg = ConvertUtils.convert(strNum, clazz.getComponentType()); - } - varArgs.add(arg); - Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); - for (int j = 0; j < varArgs.size(); j++) { - Array.set(arr, j, varArgs.get(j)); - } - args[i] = arr; - } else { - Object arg = null; - if (clazz.equals(Object.class)) { - if (FLOAT_PATTERN.matcher(argsStr).find()) { - arg = ConvertUtils.convert(strNum, Double.class); - } else { - arg = ConvertUtils.convert(strNum, Long.class); - } - } else { - arg = ConvertUtils.convert(strNum, clazz); - } - args[i] = arg; - } - argsStrContainer = this.trimArgStr(argsStrContainer, pos - 1, isVarArgs, paramTypes.length, - funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } else { - // function - ctx.funcExpression = argsStr; - Object rs = doExec(ctxNode, ctx); - if (isVarArgs && i == paramTypes.length - 1) { - Object arg = ConvertUtils.convert(rs, clazz.getComponentType()); - varArgs.add(arg); - Object arr = Array.newInstance(clazz.getComponentType(), varArgs.size()); - for (int j = 0; j < varArgs.size(); j++) { - Array.set(arr, j, varArgs.get(j)); - } - args[i] = arr; - } else { - Object arg = ConvertUtils.convert(rs, clazz); - args[i] = arg; - } - argsStr = ctx.funcExpression; - argsStrContainer.setArgsStr(argsStr); - argsStrContainer = this.trimArgStr(argsStrContainer, 0, isVarArgs, paramTypes.length, - funcExpression); - argsStr = argsStrContainer.getArgsStr(); - i = argsStrContainer.getIndex(); - } - } - ctx.funcExpression = argsStr; - } - return args; - } - - private ArgsStrContainer trimArgStr(ArgsStrContainer argsStrContainer, int fromIndex, boolean isVarArgs, - int paramTypesLen, String funcExpression) { - int i = argsStrContainer.getIndex(); - String argsStr = argsStrContainer.getArgsStr(); - if (i == paramTypesLen - 1 || (isVarArgs && i == paramTypesLen - 2)) { - boolean hasMore = hasMoreArg(argsStr, fromIndex); - if (isVarArgs && hasMore) { - argsStr = removeComma(argsStr, fromIndex, funcExpression); - if (i == paramTypesLen - 1) { - i--; - } - } else { - if (hasCloseParenthesis(argsStr, fromIndex)) { - argsStr = removeCloseParenthesis(argsStr, fromIndex, funcExpression); - i++; - } else { - throw new FizzRuntimeException(String.format("invalid argument: %s, Function Expression: %s", - argsStr.substring(fromIndex), funcExpression)); - } - } - } else { - argsStr = removeComma(argsStr, fromIndex, funcExpression); - } - argsStrContainer.setArgsStr(argsStr); - argsStrContainer.setIndex(i); - return argsStrContainer; - } - - private boolean hasMoreArg(String argsStr, int fromIndex) { - final int strLen = argsStr.length(); - if (strLen == 0) { - return false; - } - for (int i = fromIndex; i < strLen; i++) { - if (!Character.isWhitespace(argsStr.charAt(i))) { - if (",".equals(String.valueOf(argsStr.charAt(i)))) { - return true; - } else { - return false; - } - } - } - return false; - } - - private boolean hasCloseParenthesis(String argsStr, int fromIndex) { - final int strLen = argsStr.length(); - if (strLen == 0) { - return false; - } - for (int i = fromIndex; i < strLen; i++) { - if (!Character.isWhitespace(argsStr.charAt(i))) { - if (")".equals(String.valueOf(argsStr.charAt(i)))) { - return true; - } else { - return false; - } - } - } - return false; - } - - private String removeComma(String argsStr, int fromIndex, String funcExpression) { - final int strLen = argsStr.length(); - if (strLen == 0) { - return argsStr; - } - for (int i = fromIndex; i < strLen; i++) { - if (!Character.isWhitespace(argsStr.charAt(i))) { - if (",".equals(String.valueOf(argsStr.charAt(i)))) { - return StringUtils.trim(argsStr.substring(i + 1)); - } - } - } - throw new FizzRuntimeException(String.format("missing comma after argument: %s, Function Expression: %s", - argsStr.substring(fromIndex), funcExpression)); - } - - private String removeCloseParenthesis(String argsStr, int fromIndex, String funcExpression) { - final int strLen = argsStr.length(); - if (strLen == 0 || strLen < fromIndex) { - return argsStr; - } - for (int i = fromIndex; i < strLen; i++) { - if (!Character.isWhitespace(argsStr.charAt(i))) { - if (")".equals(String.valueOf(argsStr.charAt(i)))) { - return StringUtils.trim(argsStr.substring(i + 1)); - } - } - } - throw new FizzRuntimeException( - String.format("missing close parenthesis after argument: %s, Function Expression: %s", - argsStr.substring(fromIndex), funcExpression)); - } - - private int findStringEngPos(String ep) { - int pos = ep.indexOf("\"", 1); - while (pos != -1) { - String prevChar = ep.substring(pos - 1, pos); - if (!"\\".equals(prevChar)) { - return pos; - } - pos = ep.indexOf("\"", pos + 1); - } - return -1; - } - -} - -@Data -@AllArgsConstructor -class ArgsStrContainer { - private String argsStr; - private int index; -} - -@Data -@AllArgsConstructor -@NoArgsConstructor -class RecursionContext { - public String funcExpression; - public Object result; -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/IFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/IFunc.java deleted file mode 100644 index 3af99d4..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/IFunc.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -/** - * Function interface - * - * @author Francis Dong - * - */ -public interface IFunc { - - public final static String NAME_SPACE_PREFIX = "fn."; - - /** - * Init: Register functions to FuncExecutor in the initial stage
- *
- * Example:
- * FuncExecutor.register(NAME_SPACE_PREFIX + "date.timestamp", getInstance());
- * FuncExecutor.register(NAME_SPACE_PREFIX + "date.now", getInstance());
- * - */ - void init(); - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/ListFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/ListFunc.java deleted file mode 100644 index 7c41a49..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/ListFunc.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.function; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fizzgate.fizz.exception.FizzRuntimeException; - -/** - * List Functions - * - * @author Francis Dong - * - */ -public class ListFunc implements IFunc { - - private static final Logger LOGGER = LoggerFactory.getLogger(ListFunc.class); - - private static ListFunc singleton; - - public static ListFunc getInstance() { - if (singleton == null) { - synchronized (ListFunc.class) { - if (singleton == null) { - ListFunc instance = new ListFunc(); - instance.init(); - singleton = instance; - } - } - } - return singleton; - } - - private ListFunc() { - } - - public void init() { - FuncExecutor.register(NAME_SPACE_PREFIX + "list.expand", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "list.merge", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "list.extract", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "list.join", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "list.rename", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "list.removeFields", this); - } - - /** - * Expand sublist item to the first level - * - * @param data - * @return - */ - public List expand(List> data) { - List result = new ArrayList<>(); - if (data == null || data.size() == 0) { - return result; - } - for (List list : data) { - result.addAll(list); - } - return result; - } - - /** - * Merge multiple list into one list - * - * @param data - * @return - */ - public List merge(List... data) { - List result = new ArrayList<>(); - if (data == null || data.length == 0) { - return result; - } - for (List list : data) { - if (list == null || list.size() == 0) { - continue; - } - result.addAll(list); - } - return result; - } - - /** - * Extract fields from list - * - * @param data - * @param fields - * @return - */ - public List> extract(List> data, String... fields) { - List> result = new ArrayList<>(); - if (data == null || data.size() == 0) { - return result; - } - if (fields == null || fields.length == 0) { - return data; - } - for (Map m : data) { - Map r = new HashMap<>(); - for (String field : fields) { - r.put(field, m.get(field)); - } - result.add(r); - } - return result; - } - - /** - * Merge fields of source list to destination list join by the join field - * - * @param dest destination list - * @param src source list - * @param joinField join field, pattern: joinFieldOfDest:joinFieldOfSrc, - * :joinFieldOfSrc could be omitted if both join field names - * are the same - * @param fields fields which will be merge to destination list, all fields - * will be merged if it is null - * @return - */ - public List> join(List> dest, List> src, - String joinField, String... fields) { - if (dest == null || dest.size() == 0 || src == null || src.size() == 0) { - return dest; - } - String[] joinFields = joinField.split(":"); - if (joinFields.length == 1) { - joinFields = new String[] {joinField, joinField}; - } - Map> index = new HashMap<>(); - for (Map item : src) { - if (item.get(joinFields[1]) != null) { - index.putIfAbsent(item.get(joinFields[1]).toString(), item); - } - } - - for (Map m : dest) { - Object srcJoinFieldVal = m.get(joinFields[0]); - if (srcJoinFieldVal == null || !index.containsKey(srcJoinFieldVal.toString())) { - continue; - } - Map record = index.get(srcJoinFieldVal.toString()); - - if (fields == null || fields.length == 0) { - m.putAll(record); - } else { - for (String field : fields) { - m.put(field, record.get(field)); - } - } - - } - return dest; - } - - /** - * Rename fields of list - * - * @param data list - * @param fieldPairs old and new key pair of map of list, pattern: - * oldFieldName:newFieldName - * @return - */ - public List> rename(List> data, String... fieldPairs) { - if (data == null || data.size() == 0) { - return data; - } - if (fieldPairs == null || fieldPairs.length == 0) { - return data; - } - - for (Map m : data) { - for (String fieldPair : fieldPairs) { - String[] parts = fieldPair.split(":"); - if (parts == null || parts.length != 2) { - LOGGER.warn("invalid fieldPair: {} , field pair pattern is: oldFieldName:newFieldName", fieldPair); - throw new FizzRuntimeException( - "invalid fieldPair: " + fieldPair + " , field pair pattern is: oldFieldName:newFieldName"); - } - if (m.containsKey(parts[0])) { - m.put(parts[1], m.get(parts[0])); - m.remove(parts[0]); - } - } - } - return data; - } - - /** - * Remove fields from list - * - * @param data - * @param fields fields to be removed - * @return - */ - public List> removeFields(List> data, String... fields) { - if (data == null || data.size() == 0) { - return data; - } - if (fields == null || fields.length == 0) { - return data; - } - for (Map m : data) { - for (String field : fields) { - m.remove(field); - } - } - return data; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/MathFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/MathFunc.java deleted file mode 100644 index 39c5752..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/MathFunc.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import java.math.BigDecimal; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Math Functions - * - * @author Francis Dong - * - */ -@SuppressWarnings("unused") -public class MathFunc implements IFunc { - - private static final Logger LOGGER = LoggerFactory.getLogger(MathFunc.class); - - private static MathFunc singleton; - - public static MathFunc getInstance() { - if (singleton == null) { - synchronized (MathFunc.class) { - if (singleton == null) { - MathFunc instance = new MathFunc(); - instance.init(); - singleton = instance; - } - } - } - return singleton; - } - - private MathFunc() { - } - - public void init() { - FuncExecutor.register(NAME_SPACE_PREFIX + "math.absExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.negateExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.addExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.subtractExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.multiplyExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.maxExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.minExact", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.mod", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.pow", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.sqrt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.random", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.absDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.negateDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.addDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.subtractDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.multiplyDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.divideDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.maxDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.minDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.scaleDecimal", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.compare", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.equals", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.lt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.le", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.gt", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "math.ge", this); - } - - public long absExact(long a) { - return Math.abs(a); - } - - public long negateExact(long a) { - return Math.negateExact(a); - } - - public long addExact(long x, long y) { - return Math.addExact(x, y); - } - - public long subtractExact(long x, long y) { - return Math.subtractExact(x, y); - } - - public long multiplyExact(long x, long y) { - return Math.multiplyExact(x, y); - } - - public long maxExact(long x, long y) { - return Math.max(x, y); - } - - public long minExact(long x, long y) { - return Math.min(x, y); - } - - public long mod(long x, long y) { - return Math.floorMod(x, y); - } - - public double pow(double a, double b) { - return Math.pow(a, b); - } - - public double sqrt(double a) { - return Math.sqrt(a); - } - - /** - * Returns a {@code double} value with a positive sign, greater than or equal to - * {@code 0.0} and less than {@code 1.0}. Returned values are chosen - * pseudorandomly with (approximately) uniform distribution from that range. - * - * @return - */ - public double random() { - return Math.random(); - } - - public double absDecimal(double a) { - return BigDecimal.valueOf(a).abs().doubleValue(); - } - - public double negateDecimal(double a) { - return BigDecimal.valueOf(a).negate().doubleValue(); - } - - public double addDecimal(double x, double y) { - return BigDecimal.valueOf(x).add(BigDecimal.valueOf(y)).doubleValue(); - } - - public double subtractDecimal(double x, double y) { - return BigDecimal.valueOf(x).subtract(BigDecimal.valueOf(y)).doubleValue(); - } - - public double multiplyDecimal(double x, double y) { - return BigDecimal.valueOf(x).multiply(BigDecimal.valueOf(y)).doubleValue(); - } - - public double divideDecimal(double x, double y) { - return BigDecimal.valueOf(x).divide(BigDecimal.valueOf(y)).doubleValue(); - } - - public double maxDecimal(double x, double y) { - return BigDecimal.valueOf(x).max(BigDecimal.valueOf(y)).doubleValue(); - } - - public double minDecimal(double x, double y) { - return BigDecimal.valueOf(x).min(BigDecimal.valueOf(y)).doubleValue(); - } - - public double scaleDecimal(double a, int scale) { - return BigDecimal.valueOf(a).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); - } - - /** - * Compares number x with the specified number y. - * - * @param x number - * @param y number - * @return -1, 0, or 1 as x is numerically less than, equal to, or greater than - * y. - */ - public int compare(double x, double y) { - return BigDecimal.valueOf(x).compareTo(BigDecimal.valueOf(y)); - } - - public boolean equals(double x, double y) { - return BigDecimal.valueOf(x).equals(BigDecimal.valueOf(y)); - } - - /** - * Checks if x is less than y - * @param x - * @param y - * @return - */ - public boolean lt(double x, double y) { - return BigDecimal.valueOf(x).compareTo(BigDecimal.valueOf(y)) == -1; - } - - /** - * Checks if x is less than or equals y - * @param x - * @param y - * @return - */ - public boolean le(double x, double y) { - return BigDecimal.valueOf(x).compareTo(BigDecimal.valueOf(y)) <= 0; - } - - /** - * Checks if x is greater than y - * @param x - * @param y - * @return - */ - public boolean gt(double x, double y) { - return BigDecimal.valueOf(x).compareTo(BigDecimal.valueOf(y)) == 1; - } - - /** - * Checks if x is greater than or equals y - * @param x - * @param y - * @return - */ - public boolean ge(double x, double y) { - return BigDecimal.valueOf(x).compareTo(BigDecimal.valueOf(y)) >= 0; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/function/StringFunc.java b/fizz-core/src/main/java/com/fizzgate/fizz/function/StringFunc.java deleted file mode 100644 index cbd9293..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/function/StringFunc.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import java.util.UUID; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fizzgate.fizz.exception.FizzRuntimeException; - -/** - * String Functions - * - * @author Francis Dong - * - */ -public class StringFunc implements IFunc { - - private static final Logger LOGGER = LoggerFactory.getLogger(StringFunc.class); - - private static StringFunc singleton; - - public static StringFunc getInstance() { - if (singleton == null) { - synchronized (StringFunc.class) { - if (singleton == null) { - StringFunc instance = new StringFunc(); - instance.init(); - singleton = instance; - } - } - } - return singleton; - } - - private StringFunc() { - } - - public void init() { - FuncExecutor.register(NAME_SPACE_PREFIX + "string.equals", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.equalsIgnoreCase", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.compare", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.concat", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.concatws", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.substring", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.indexOf", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.startsWith", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.endsWith", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.toUpperCase", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.toLowerCase", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.uuid", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.toString", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.replace", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.replaceAll", this); - FuncExecutor.register(NAME_SPACE_PREFIX + "string.replaceFirst", this); - } - - /** - *

- * Compares two Strings, returning {@code true} if they represent equal - * sequences of Strings. - *

- * - *

- * {@code null}s are handled without exceptions. Two {@code null} references are - * considered to be equal. The comparison is case sensitive. - *

- * - *
-	 * equals(null, null)   = true
-	 * equals(null, "abc")  = false
-	 * equals("abc", null)  = false
-	 * equals("abc", "abc") = true
-	 * equals("abc", "ABC") = false
-	 * 
- * - * @param str1 the first String, may be {@code null} - * @param str2 the second String, may be {@code null} - * @return {@code true} if the Strings are equal (case-sensitive), or both - * {@code null} - */ - public boolean equals(String str1, String str2) { - return StringUtils.equals(str1, str2); - } - - /** - *

- * Compares two Strings, returning {@code true} if they represent equal - * sequences of Strings, ignoring case. - *

- * - *

- * {@code null}s are handled without exceptions. Two {@code null} references are - * considered equal. The comparison is case insensitive. - *

- * - *
-	 * equalsIgnoreCase(null, null)   = true
-	 * equalsIgnoreCase(null, "abc")  = false
-	 * equalsIgnoreCase("abc", null)  = false
-	 * equalsIgnoreCase("abc", "abc") = true
-	 * equalsIgnoreCase("abc", "ABC") = true
-	 * 
- * - * @param str1 the first String, may be {@code null} - * @param str2 the second String, may be {@code null} - * @return {@code true} if the Strings are equal (case-insensitive), or both - * {@code null} - */ - public boolean equalsIgnoreCase(String str1, String str2) { - return StringUtils.equalsIgnoreCase(str1, str2); - } - - /** - * Compare two Strings lexicographically - * - * @param str1 - * @param str2 - * @return -1, 0, 1, if {@code str1} is respectively less, equal or greater than - * {@code str2} - */ - public int compare(String str1, String str2) { - int n = StringUtils.compare(str1, str2); - return n == 0 ? 0 : (n > 0 ? 1 : -1); - } - - /** - * Concat strings - * - * @param strs - * @return - */ - public String concat(String... strs) { - return StringUtils.join(strs); - } - - /** - * Concat with separator - * - * @param strs - * @return - */ - public String concatws(String separator, String... strs) { - return StringUtils.join(strs, separator); - } - - /** - * Returns a string that is a substring of this string. The substring begins at - * the specified {@code beginIndex} and extends to the character at index - * {@code endIndex - 1}. Thus the length of the substring is - * {@code endIndex-beginIndex}. - * - * @param str - * @param beginIndex - * @param endIndex - * @return - */ - public String substring(String str, int beginIndex, int... endIndex) { - if (StringUtils.isBlank(str)) { - return str; - } - if (endIndex != null && endIndex.length > 0) { - if (endIndex.length > 1) { - LOGGER.error("invalid argument: endIndex"); - throw new FizzRuntimeException("invalid argument: endIndex"); - } - return str.substring(beginIndex, endIndex[0]); - } - return str.substring(beginIndex); - } - - /** - * Returns the index within this string of the first occurrence of the specified - * substring. - * - * @param str - * @param substr - * @return the index of the first occurrence of the specified substring, or - * {@code -1} if there is no such occurrence. - */ - public int indexOf(String str, String substr) { - if (StringUtils.isBlank(str)) { - return -1; - } - return str.indexOf(substr); - } - - /** - * Tests if this string starts with the specified prefix. - * - * @param prefix the prefix. - * @return {@code true} if the character sequence represented by the argument is - * a prefix of the character sequence represented by this string; - * {@code false} otherwise. Note also that {@code true} will be returned - * if the argument is an empty string or is equal to this {@code String} - * object as determined by the {@link #equals(Object)} method. - */ - public boolean startsWith(String str, String prefix) { - if (StringUtils.isBlank(str)) { - return false; - } - return str.startsWith(prefix); - } - - /** - * Tests if this string starts with the specified prefix. - * - * @param prefix the prefix. - * @return {@code true} if the character sequence represented by the argument is - * a prefix of the character sequence represented by this string; - * {@code false} otherwise. Note also that {@code true} will be returned - * if the argument is an empty string or is equal to this {@code String} - * object as determined by the {@link #equals(Object)} method. - */ - public boolean endsWith(String str, String suffix) { - if (StringUtils.isBlank(str)) { - return false; - } - return str.endsWith(suffix); - } - - public String toUpperCase(String str) { - if (StringUtils.isBlank(str)) { - return str; - } - return str.toUpperCase(); - } - - public String toLowerCase(String str) { - if (StringUtils.isBlank(str)) { - return str; - } - return str.toLowerCase(); - } - - /** - * - * @return UUID - */ - public String uuid() { - return UUID.randomUUID().toString().replaceAll("-", ""); - } - - public String toString(Object obj) { - return obj == null ? null : obj.toString(); - } - - /** - * Replaces each substring of this string that matches the literal target - * sequence with the specified literal replacement sequence. The replacement - * proceeds from the beginning of the string to the end, for example, replacing - * "aa" with "b" in the string "aaa" will result in "ba" rather than "ab". - * - * @param str String - * @param target The sequence of char values to be replaced - * @param replacement The replacement sequence of char values - * @return - */ - public String replace(String str, String target, String replacement) { - return str.replace(target, replacement); - } - - /** - * Replaces each substring of this string that matches the given regular - * expression with the given replacement. - * - * @param str String - * @param regex the regular expression to which this string is to be - * matched - * @param replacement the string to be substituted for each match - * @return - */ - public String replaceAll(String str, String regex, String replacement) { - return str.replaceAll(regex, replacement); - } - - /** - * Replaces the first substring of this string that matches the given regular - * expression with the given replacement. - * - * @param str String - * @param regex the regular expression to which this string is to be - * matched - * @param replacement the string to be substituted for the first match - * @return - */ - public String replaceFirst(String str, String regex, String replacement) { - return str.replaceFirst(regex, replacement); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/ClientInputConfig.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/ClientInputConfig.java deleted file mode 100644 index 8bdffda..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/ClientInputConfig.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class ClientInputConfig extends InputConfig { - - private boolean debug; - private String path; - private String method; - private Map headers = new HashMap(); - private Map langDef; - private Map bodyDef; - private Map headersDef; - private Map paramsDef; - private Map scriptValidate; - private Map validateResponse; - private String contentType; - private String xmlArrPaths; - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public ClientInputConfig(Map configBody) { - super(configBody); - if(configBody.get("debug") != null) { - this.debug = (boolean) configBody.get("debug"); - } - this.path = (String) configBody.get("path"); - if (configBody.get("headers") != null) { - setHeaders((Map) configBody.get("headers")); - } - if (configBody.get("method") != null) { - setMethod((String) configBody.get("method")); - } else { - setMethod("GET"); - } - - if (configBody.get("langDef") != null) { - langDef = (Map) configBody.get("langDef"); - } - if (configBody.get("bodyDef") != null) { - bodyDef = (Map) configBody.get("bodyDef"); - } - if (configBody.get("paramsDef") != null) { - paramsDef = (Map) configBody.get("paramsDef"); - } - if (configBody.get("headersDef") != null) { - headersDef = (Map) configBody.get("headersDef"); - } - if (configBody.get("scriptValidate") != null) { - scriptValidate = (Map) configBody.get("scriptValidate"); - } - if (configBody.get("validateResponse") != null) { - validateResponse = (Map) configBody.get("validateResponse"); - } - if (configBody.get("contentType") != null) { - contentType = (String) configBody.get("contentType"); - } - if (configBody.get("xmlArrPaths") != null) { - xmlArrPaths = (String) configBody.get("xmlArrPaths"); - } - } - - public ClientInputConfig() { - super(null); - } - - - public boolean isDebug() { - return debug; - } - - public void setDebug(boolean debug) { - this.debug = debug; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public Map getLangDef() { - return langDef; - } - - public void setLangDef(Map langDef) { - this.langDef = langDef; - } - - public Map getHeaders() { - return headers; - } - - public void setHeaders(Map headers) { - this.headers = headers; - } - - public String getMethod() { - return method; - } - - public void setMethod(String method) { - this.method = method; - } - - public Map getBodyDef() { - return bodyDef; - } - - public void setBodyDef(Map bodyDef) { - this.bodyDef = bodyDef; - } - - public Map getHeadersDef() { - return headersDef; - } - - public void setHeadersDef(Map headersDef) { - this.headersDef = headersDef; - } - - public Map getParamsDef() { - return paramsDef; - } - - public void setParamsDef(Map paramsDef) { - this.paramsDef = paramsDef; - } - - public Map getScriptValidate() { - return scriptValidate; - } - - public void setScriptValidate(Map scriptValidate) { - this.scriptValidate = scriptValidate; - } - - public Map getValidateResponse() { - return validateResponse; - } - - public void setValidateResponse(Map validateResponse) { - this.validateResponse = validateResponse; - } - - public String getContentType() { - return contentType; - } - - public void setContentType(String contentType) { - this.contentType = contentType; - } - - public String getXmlArrPaths() { - return xmlArrPaths; - } - - public void setXmlArrPaths(String xmlArrPaths) { - this.xmlArrPaths = xmlArrPaths; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/IInput.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/IInput.java deleted file mode 100644 index 434f7a3..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/IInput.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import org.springframework.context.ConfigurableApplicationContext; - -import com.fizzgate.fizz.Step; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.StepResponse; - -import reactor.core.publisher.Mono; - -import java.lang.ref.SoftReference; -import java.lang.reflect.Field; -import java.util.Map; - -public interface IInput { - public static final InputType TYPE = null; - public static Class inputConfigClass() { - return null; - } - public String getName() ; - public boolean needRun(StepContext stepContext); - public void beforeRun(InputContext context); - public Mono run(); - - public StepResponse getStepResponse() ; - public void setStepResponse(StepResponse stepResponse); - public SoftReference getWeakStep(); - public void setWeakStep(SoftReference weakStep); - - public ConfigurableApplicationContext getCurrentApplicationContext(); - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/Input.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/Input.java deleted file mode 100644 index 70cc810..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/Input.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.input; - -import java.lang.ref.SoftReference; -import java.lang.reflect.Field; -import java.util.Map; - - -import org.reflections.Reflections; -import org.springframework.context.ConfigurableApplicationContext; - -import com.fizzgate.fizz.Step; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.StepResponse; - -import reactor.core.publisher.Mono; - -/** - * - * @author linwaiwai - * - */ -public class Input { - protected String name; - protected InputConfig config; - protected InputContext inputContext; - protected StepResponse lastStepResponse = null; - protected StepResponse stepResponse; - private SoftReference weakStep; - public void setConfig(InputConfig inputConfig) { - config = inputConfig; - } - public InputConfig getConfig() { - return config; - } - - public void beforeRun(InputContext context) { - this.inputContext = context; - } - - public String getName() { - if (name == null) { - return name = "input" + (int)(Math.random()*100); - } - return name; - } - - /** - * 检查该Input是否需要运行,默认都运行 - * @stepContext Step上下文 - * @return TRUE:运行 - */ - public boolean needRun(StepContext stepContext) { - return Boolean.TRUE; - } - - public Mono run() { - return null; - } - public void setName(String configName) { - this.name = configName; - - } - - public StepResponse getStepResponse() { - return stepResponse; - } - public void setStepResponse(StepResponse stepResponse) { - this.stepResponse = stepResponse; - } - - public SoftReference getWeakStep() { - return weakStep; - } - - public void setWeakStep(SoftReference weakStep) { - this.weakStep = weakStep; - } - - public ConfigurableApplicationContext getCurrentApplicationContext(){ - return this.getWeakStep() != null ? this.getWeakStep().get().getCurrentApplicationContext() : null; - } - - public static Class inputConfigClass (){ - return InputConfig.class; - } - - public static void initialize(Classclazz) throws IllegalAccessException { - Field field = null; - try { - field = clazz.getDeclaredField("TYPE"); - InputFactory.registerInput((InputType) field.get(null), clazz); - } catch (NoSuchFieldException e) { - // doing nothing is right - } - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputConfig.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/InputConfig.java deleted file mode 100644 index dc45fed..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputConfig.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.fizzgate.fizz.component.IComponent; - -/** - * - * @author linwaiwai - * - */ -public class InputConfig { - - private InputType type; - protected Map dataMapping; - protected Map configMap; - private Map condition; - private List components; - - public Map getCondition() { - return condition; - } - - public void setCondition(Map condition) { - this.condition = condition; - } - - public InputConfig(Map aConfigMap) { - configMap = aConfigMap; - } - - public InputType getType() { - return type; - } - - public void setType(InputType typeEnum) { - this.type = typeEnum; - } - - public Map getDataMapping() { - return dataMapping; - } - - public void setDataMapping(Map dataMapping) { - this.dataMapping = dataMapping; - } - - private Map fallback = new HashMap(); - - public Map getFallback() { - return fallback; - } - - public void setFallback(Map fallback) { - this.fallback = fallback; - } - - public List getComponents() { - return components; - } - - public void setComponents(List components) { - this.components = components; - } - - public void parse(){ - - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputContext.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/InputContext.java deleted file mode 100644 index 95759ac..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputContext.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -package com.fizzgate.fizz.input; - -import java.util.HashMap; -import java.util.Map; - -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.StepResponse; - -/** - * - * @author linwaiwai - * - */ -public class InputContext { - private StepContext stepContext; - private StepResponse lastStepResponse = null; - public InputContext(StepContext stepContext2, StepResponse lastStepResponse2) { - this.stepContext = stepContext2; - this.lastStepResponse = lastStepResponse2; - } - public StepContext getStepContext() { - return stepContext; - } - public void setStepContext(StepContext stepContext) { - this.stepContext = stepContext; - } - public StepResponse getLastStepResponse() { - return lastStepResponse; - } - public void setLastStepResponse(StepResponse lastStepResponse) { - this.lastStepResponse = lastStepResponse; - } -// public Map getResponses() { -// //TODO: -// if (stepContext != null) { -// Map responses = new HashMap(); -// for( String key :stepContext.keySet()) { -// StepResponse stepResponse = (StepResponse)stepContext.get(key); -// responses.put(key, stepResponse.getResponse()); -// } -// return responses; -// } else { -// return null; -// } -// -// -// -// } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputFactory.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/InputFactory.java deleted file mode 100644 index 8ce98ad..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputFactory.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import org.reflections.Reflections; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fizzgate.fizz.component.ComponentExecutor; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.input.extension.request.RequestInput; - -/** - * - * @author linwaiwai - * - */ -public class InputFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(InputFactory.class); - public static Map inputClasses = new HashMap(); - public static void registerInput(InputType type, Class inputClass){ - inputClasses.put(type, inputClass); - } - public static void unregisterInput(InputType type){ - inputClasses.remove(type); - } - public static InputConfig createInputConfig(Map config) { - String type = (String) config.get("type"); - InputType typeEnum = InputType.valueOf(type.toUpperCase()); - InputConfig inputConfig = null; - if (inputClasses.containsKey(typeEnum)){ - Class InputClass = inputClasses.get(typeEnum); - - try { - Method inputConfigClassMethod = InputClass.getMethod("inputConfigClass"); - Class InputConfigClass = (Class) inputConfigClassMethod.invoke(null); - Constructor constructor = null; - constructor = InputConfigClass.getDeclaredConstructor(Map.class); - constructor.setAccessible(true); - inputConfig = (InputConfig) constructor.newInstance(config); - } catch (Exception e) { - LOGGER.error("failed to create input config, error: {}", e.getMessage(), e); - throw new FizzRuntimeException("failed to create input config, message: " + e.getMessage(), e); - } - inputConfig.setType(typeEnum); - inputConfig.setDataMapping((Map) config.get("dataMapping")); - inputConfig.setComponents(ComponentExecutor.buildComponents((List>) config.get("components"))); - inputConfig.parse(); - return inputConfig; - } else { - throw new FizzRuntimeException("can't find input config type:" + type); - } - } - - public static Input createInput(String type) { - InputType typeEnum = InputType.valueOf(type.toUpperCase()); - Input input = null; - if (inputClasses.containsKey(typeEnum)) { - Class InputClass = inputClasses.get(typeEnum); - Constructor constructor = null; - try { - constructor = InputClass.getDeclaredConstructor(); - constructor.setAccessible(true); - input = (Input) constructor.newInstance(); - return input; - } catch (Exception e) { - LOGGER.error("failed to create input config, error: {}", e.getMessage(), e); - throw new FizzRuntimeException("failed to create input config, message: " + e.getMessage(), e); - } - } else { - throw new FizzRuntimeException("can't find input type:" + type); - } - } - - public static void loadInputClasses() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - Reflections reflections = new Reflections("com.fizzgate.fizz.input"); - Set> subTypes = reflections.getSubTypesOf(Input.class); - for (ClassinputType : subTypes){ - Method initializeMethod = inputType.getMethod("initialize", Class.class); - initializeMethod.invoke(null, inputType); - } - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputType.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/InputType.java deleted file mode 100644 index e39e0fe..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/InputType.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author linwaiwai - * - */ - -public class InputType { - - private final String type; - static private Map inputs = new HashMap(); - public InputType(String aType) { - this.type = aType; - inputs.put(aType, this); - } - - public static InputType valueOf(String string) { - return inputs.get(string); - } - public String toString(){ - return type; - } - -} \ No newline at end of file diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/PathMapping.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/PathMapping.java deleted file mode 100644 index f88f4a4..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/PathMapping.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; - -import org.apache.commons.lang3.StringUtils; -import org.noear.snack.ONode; - -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.function.FuncExecutor; -import com.fizzgate.fizz.function.IFunc; -import com.fizzgate.global_resource.GlobalResourceService; -import com.fizzgate.util.MapUtil; - -/** - * - * @author Francis Dong - * - */ -public class PathMapping { - - private static final String GLOBAL_RESOURCE_PREFIX = "g."; - - private static List typeList = Arrays.asList("Integer", "int", "Boolean", "boolean", "Float", "float", - "Double", "double", "String", "string", "Long", "long", "Number", "number"); - - public static ONode toONode(Object obj) { - ONode o = null; - synchronized (obj) { - o = ONode.loadObj(obj); - } - return o; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void setByPath(ONode target, String path, Object obj, boolean supportMultiLevels) { - if (CommonConstants.WILDCARD_STAR.equals(path)) { - if (obj instanceof ONode) { - ONode node = (ONode) obj; - if(node.isObject()) { - target.setAll(node); - } - } else if (obj instanceof Map) { - target.setAll((Map) obj); - } - } else { - String[] keys = path.split("\\."); - if (!supportMultiLevels) { - keys = new String[] { path }; - } - ONode cur = target; - for (int i = 0; i < keys.length - 1; i++) { - cur = cur.getOrNew(keys[i]); - } - - if ((obj instanceof ONode && ((ONode) obj).isArray()) || obj instanceof Collection - || (obj instanceof ONode && ((ONode) obj).isObject()) || obj instanceof Map) { - ONode subNode = cur.getOrNew(keys[keys.length - 1]); - if ((obj instanceof ONode && ((ONode) obj).isArray()) || obj instanceof Collection) { - if (subNode.isArray()) { - if (obj instanceof ONode) { - subNode.addAll((ONode) obj); - } else if (obj instanceof Collection) { - subNode.addAll((Collection) obj); - } - } else { - subNode.fill(obj); - } - } else { - if (subNode.isObject()) { - if (obj instanceof ONode) { - ONode node = (ONode) obj; - if (node.isObject()) { - subNode.setAll(node); - } - } else if (obj instanceof Map) { - subNode.setAll((Map) obj); - } - } else { - subNode.fill(obj); - } - } - } else { - cur.set(keys[keys.length - 1], obj); - } - } - } - - public static Map transformToMap(ONode ctxNode, Map rules, boolean supportMultiLevels) { - ONode target = transform(ctxNode, rules, supportMultiLevels); - return target.toObject(Map.class); - } - - @SuppressWarnings("unchecked") - public static ONode transform(ONode ctxNode, Map rules, boolean supportMultiLevels) { - ONode target = ONode.load(new HashMap()); - if (rules.isEmpty()) { - return target; - } - - Map rs = new HashMap<>(); - Map types = new HashMap<>(); - for (Entry entry : rules.entrySet()) { - if (entry.getValue() instanceof String) { - String val = (String) entry.getValue(); - Optional optType = typeList.stream().filter(s -> val.startsWith(s + " ")).findFirst(); - if (optType.isPresent()) { - rs.put(entry.getKey(), val.substring(optType.get().length() + 1)); - types.put(entry.getKey(), optType.get()); - } else { - rs.put(entry.getKey(), val); - } - } else if (entry.getValue() instanceof List) { - List values = (List) entry.getValue(); - List vList = new ArrayList<>(); - List tList = new ArrayList<>(); - for (Object v : values) { - if (v instanceof String) { - String val = (String) v; - Optional optType = typeList.stream().filter(s -> val.startsWith(s + " ")).findFirst(); - if (optType.isPresent()) { - vList.add(val.substring(optType.get().length() + 1)); - tList.add(optType.get()); - } else { - vList.add(val); - tList.add(null); - } - } - } - rs.put(entry.getKey(), vList); - types.put(entry.getKey(), tList); - } - } - - if (rs.isEmpty()) { - return target; - } - - // wildcard star entry - Object starValObj = null; - String starEntryKey = null; - - for (Entry entry : rs.entrySet()) { - if (entry.getValue() instanceof String) { - String path = (String) entry.getValue(); - String type = (String) types.get(entry.getKey()); - Object obj = getRefValue(ctxNode, type, path); - if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) { - starValObj = obj; - starEntryKey = entry.getKey(); - } else { - setByPath(target, entry.getKey(), obj, supportMultiLevels); - } - } else if (entry.getValue() instanceof List) { - List refs = (List) entry.getValue(); - List tList = (List) types.get(entry.getKey()); - List refValList = new ArrayList<>(); - for (int i = 0; i < refs.size(); i++) { - String path = refs.get(i); - String type = tList.get(i); - Object obj = getRefValue(ctxNode, type, path); - // Only header form-data and query Parameter support multiple values, merge result into - // one a list - if (obj instanceof List) { - refValList.addAll((List) obj); - } else { - refValList.add(obj); - } - } - setByPath(target, entry.getKey(), refValList, supportMultiLevels); - } - } - - if(starEntryKey != null) { - setByPath(target, starEntryKey, starValObj, supportMultiLevels); - } - return target; - } - - private static Object getRefValue(ONode ctxNode, String type, String path) { - if (StringUtils.isBlank(path)) { - return null; - } - Object obj = null; - // check if it is a function - if (path.startsWith(IFunc.NAME_SPACE_PREFIX)) { - obj = FuncExecutor.getInstance().exec(ctxNode, path); - if (obj != null && type != null) { - obj = cast(obj, type, path); - } - } else { - try { - String p = path; - String defaultValue = null; - if (path.indexOf("|") != -1) { - p = path.substring(0, path.indexOf("|")); - defaultValue = path.substring(path.indexOf("|") + 1); - } - ONode val = null; - if (p.startsWith(GLOBAL_RESOURCE_PREFIX)) { - val = select(GlobalResourceService.resNode, p.substring(GLOBAL_RESOURCE_PREFIX.length())); - } else { - val = select(ctxNode, handlePath(p)); - } - if (val != null && !val.isNull()) { - obj = val; - } else { - obj = defaultValue; - } - if (obj != null && type != null) { - obj = cast(obj, type, path); - } - } catch (Exception e) { - // e.printStackTrace(); - throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e); - } - } - return obj; - } - - private static Object cast(Object obj, String type, String path) { - try { - switch (type) { - case "Integer": - case "int": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getInt(); - } else { - obj = Integer.valueOf(obj.toString()); - } - break; - } - case "Boolean": - case "boolean": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getBoolean(); - } else { - obj = Boolean.valueOf(obj.toString()); - } - break; - } - case "Float": - case "float": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getFloat(); - } else { - obj = Float.valueOf(obj.toString()); - } - break; - } - case "Double": - case "double": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getDouble(); - } else { - obj = Double.valueOf(obj.toString()); - } - break; - } - case "String": - case "string": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getString(); - } else { - obj = String.valueOf(obj.toString()); - } - break; - } - case "Long": - case "long": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getLong(); - } else { - obj = Long.valueOf(obj.toString()); - } - break; - } - } - return obj; - } catch (Exception e) { - // e.printStackTrace(); - throw new FizzRuntimeException(String.format("failed to cast %s to %s, JSON path expression: %s, error: %s", obj, type, path, e.getMessage()), e); - } - } - - public static ONode select(ONode ctxNode, String path) { - ONode val = ctxNode.select("$." + path); - if (val != null && !val.isNull()) { - return val; - } - String[] arr = path.split("\\."); - if (arr.length == 6 && "headers".equals(arr[4]) && arr[5].endsWith("[0]")) { - ONode v = ctxNode.select("$." + path.substring(0, path.length() - 3)); - if (!v.isArray()) { - return v; - } - } - if (arr.length == 4 && "headers".equals(arr[2]) && arr[3].endsWith("[0]")) { - ONode v = ctxNode.select("$." + path.substring(0, path.length() - 3)); - if (!v.isArray()) { - return v; - } - } - return val; - } - - /** - * Returns value of path, return default value if no value matched by path - * - * @param ctxNode - * @param path e.g: step1.request1.headers.abc or - * step1.request1.headers.abc|123 (default value separate by "|") - * @return - */ - public static Object getValueByPath(ONode ctxNode, String path) { - return getValueByPath(ctxNode, null, path); - } - - /** - * Returns value of path, return default value if no value matched by path - * - * @param ctxNode - * @param type - * @param path e.g: step1.request1.headers.abc or - * step1.request1.headers.abc|123 (default value separate by "|") - * @return - */ - public static Object getValueByPath(ONode ctxNode, String type, String path) { -// if (StringUtils.isBlank(path)) { -// return null; -// } -// String p = path; -// String defaultValue = null; -// if (path.indexOf("|") != -1) { -// p = path.substring(0, path.indexOf("|")); -// defaultValue = path.substring(path.indexOf("|") + 1); -// } -// ONode val = null; -// if (p.startsWith(GLOBAL_RESOURCE_PREFIX)) { -// val = select(GlobalResourceService.resNode, p.substring(GLOBAL_RESOURCE_PREFIX.length())); -// } else { -// val = select(ctxNode, handlePath(p)); -// } -// if (val != null && !val.isNull()) { -// return val.toData(); -// } -// return defaultValue; - Object val = getRefValue(ctxNode, type, path); - if (val != null && val instanceof ONode) { - ONode oval = (ONode)val; - if (!oval.isNull()) { - return oval.toData(); - } else { - return val; - } - } - return val; - } - - public static Map getScriptRules(Map rules) { - if (rules.isEmpty()) { - return new HashMap<>(); - } - Map rs = new HashMap<>(); - for (Entry entry : rules.entrySet()) { - if (entry.getValue() instanceof List) { - List values = (List) entry.getValue(); - for (Object v : values) { - if (!(v instanceof String)) { - rs.put(entry.getKey(), v); - } - } - } else if (!(entry.getValue() instanceof String) && entry.getValue() instanceof Map) { - rs.put(entry.getKey(), entry.getValue()); - } - } - return rs; - } - - /** - * 把Path转为context里的实际路径
- * 步骤兼容以下几种写法,把后几种转换为第一种标准路径
- * - * 例子1:
- * step1.requests.request1.request.headers
- * step1.request1.request.headers
- * step1.request1.requestHeaders
- * step1.requests.request1.requestHeaders
- * - * 例子2:
- * step1.requests.request1.response.body
- * step1.request1.response.body
- * step1.request1.responseBody
- * step1.requests.request1.responseBody
- * - * input兼容以下写法,把第二种转换为第一种标准路径
- * - * 例子1:
- * input.request.headers
- * input.requestHeaders
- * - * 例子2:
- * input.response.body
- * input.responseBody
- * - * @param path - * @return - */ - public static String handlePath(String path) { - if(path.startsWith("step")) { - String[] arr = path.split("\\."); - - List list = Arrays.stream(arr).collect(Collectors.toList()); - // 补齐 requests - // fix-如果是从step*.result下获取数据不应该插入requests - if(list.size() >= 2 && !"requests".equals(list.get(1)) && !"result".equals(list.get(1))) { - list.add(1,"requests"); - } - - // 拆分一级为两级,如:requestBody -> request.body - if(list.size() >= 4) { - String s = list.get(3); - switch (s) { - case "requestHeaders": - list.set(3, "headers"); - list.add(3, "request"); - break; - case "requestParams": - list.set(3, "params"); - list.add(3, "request"); - break; - case "requestBody": - list.set(3, "body"); - list.add(3, "request"); - break; - case "responseHeaders": - list.set(3, "headers"); - list.add(3, "response"); - break; - case "responseBody": - list.set(3, "body"); - list.add(3, "response"); - break; - } - } - - // upper case header name - if (list.size() > 5 && "headers".equals(list.get(4))) { - String headerName = list.get(5).toUpperCase(); - list.set(5, headerName); - } - return String.join(".", list); - }else if(path.startsWith("input")) { - String[] arr = path.split("\\."); - - List list = Arrays.stream(arr).collect(Collectors.toList()); - - // 拆分一级为两级,如:requestBody -> request.body - if(list.size() >= 2) { - String s = list.get(1); - switch (s) { - case "requestHeaders": - list.set(1, "headers"); - list.add(1, "request"); - break; - case "requestParams": - list.set(1, "params"); - list.add(1, "request"); - break; - case "requestBody": - list.set(1, "body"); - list.add(1, "request"); - break; - case "responseHeaders": - list.set(1, "headers"); - list.add(1, "response"); - break; - case "responseBody": - list.set(1, "body"); - list.add(1, "response"); - break; - } - } - // upper case header name - if (list.size() > 3 && "headers".equals(list.get(2))) { - String headerName = list.get(3).toUpperCase(); - list.set(3, headerName); - } - return String.join(".", list); - - }else { - return path; - } - } - - /** - * 数据转换 - * - * @param ctxNode - * @param stepContext - * @param fixed optional - * @param mappingRules optional - * @return - */ - public static Map transform(ONode ctxNode, StepContext stepContext, - Map fixed, Map mappingRules) { - return transform(ctxNode, stepContext, fixed, mappingRules, true); - } - - /** - * 数据转换 - * - * @param ctxNode - * @param stepContext - * @param fixed optional - * @param mappingRules optional - * @return - */ - public static Map transform(ONode ctxNode, StepContext stepContext, - Map fixed, Map mappingRules, boolean supportMultiLevels) { - try { - if (fixed != null && fixed.containsKey(CommonConstants.WILDCARD_TILDE)) { - Object val = fixed.get(CommonConstants.WILDCARD_TILDE); - fixed = new HashMap<>(); - fixed.put(CommonConstants.WILDCARD_TILDE, val); - } - if (mappingRules != null && mappingRules.containsKey(CommonConstants.WILDCARD_TILDE)) { - Object val = mappingRules.get(CommonConstants.WILDCARD_TILDE); - mappingRules = new HashMap<>(); - mappingRules.put(CommonConstants.WILDCARD_TILDE, val); - } - Map result = new HashMap<>(); - if (fixed != null) { - result.putAll((Map) convertPath(fixed, supportMultiLevels)); - } - if (mappingRules != null) { - // 路径映射 - ONode target = transform(ctxNode, mappingRules, supportMultiLevels); - // 脚本转换 - Map scriptRules = getScriptRules(mappingRules); - Map scriptResult = ScriptHelper.executeScripts(target, scriptRules, ctxNode, stepContext, supportMultiLevels); - if (scriptResult != null && !scriptResult.isEmpty()) { - result = MapUtil.merge(result, scriptResult); - } - } - return result; - }catch(FizzRuntimeException e) { - throw new FizzRuntimeException(e.getMessage(), e, stepContext); - } - } - - public static Map convertPath(Map fixed, boolean supportMultiLevels) { - ONode target = ONode.load(new HashMap()); - if (fixed.isEmpty()) { - return target.toObject(Map.class); - } - - // wildcard star entry - Object starValObj = null; - String starEntryKey = null; - - for (Entry entry : fixed.entrySet()) { - if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) { - starValObj = entry.getValue(); - starEntryKey = entry.getKey(); - }else { - setByPath(target, entry.getKey(), entry.getValue(), supportMultiLevels); - } - } - if(starEntryKey != null) { - setByPath(target, starEntryKey, starValObj, supportMultiLevels); - } - - return target.toObject(Map.class); - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/RPCInput.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/RPCInput.java deleted file mode 100644 index d676d16..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/RPCInput.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import org.apache.logging.log4j.ThreadContext; -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; - -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; - -import reactor.core.publisher.Mono; - -import javax.script.ScriptException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class RPCInput extends Input { - protected static final Logger LOGGER = LoggerFactory.getLogger(RPCInput.class.getName()); - protected static final String FALLBACK_MODE_STOP = "stop"; - protected static final String FALLBACK_MODE_CONTINUE = "continue"; - protected Map request = new ConcurrentHashMap<>(); - protected Map response = new ConcurrentHashMap<>(); - - protected void doRequestMapping(InputConfig aConfig, InputContext inputContext) { - - } - - protected void doOnResponseSuccess(RPCResponse cr, long elapsedMillis) { - - } - protected Mono bodyToMono(RPCResponse cr){ - return cr.getBodyMono(); - } - - protected void doOnBodyError(Throwable ex, long elapsedMillis) { - } - - protected void doOnBodySuccess(Object resp, long elapsedMillis) { - } - - protected void doResponseMapping(InputConfig aConfig, InputContext inputContext, Object responseBody) { - } - - @Override - @SuppressWarnings("unchecked") - public boolean needRun(StepContext stepContext) { - Map condition = ((InputConfig) config).getCondition(); - if (CollectionUtils.isEmpty(condition)) { - // 没有配置condition,直接运行 - return Boolean.TRUE; - } - - ONode ctxNode = PathMapping.toONode(stepContext); - try { - Boolean needRun = ScriptHelper.execute(condition, ctxNode, stepContext, Boolean.class); - return needRun != null ? needRun : Boolean.TRUE; - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(condition), e); - throw new ExecuteScriptException(e, stepContext, condition); - } - } - - protected String prefix; - - @Override - public Mono run() { - long t1 = System.currentTimeMillis(); - this.doRequestMapping(config, inputContext); - inputContext.getStepContext().addElapsedTime(stepResponse.getStepName() + "-" + this.name + "-RequestMapping", - System.currentTimeMillis() - t1); - - prefix = stepResponse.getStepName() + "-" + "调用接口"; - long start = System.currentTimeMillis(); - Mono rpcResponse = this.getClientSpecFromContext(config, inputContext); - Mono body = rpcResponse.flatMap(cr->{ - return Mono.just(cr).doOnError(throwable -> cleanup(cr)); - }).doOnSuccess(cr -> { - long elapsedMillis = System.currentTimeMillis() - start; - this.doOnResponseSuccess(cr, elapsedMillis); - - }).flatMap(cr -> { return this.bodyToMono(cr); }).doOnSuccess(resp -> { - long elapsedMillis = System.currentTimeMillis() - start; - this.doOnBodySuccess(resp, elapsedMillis); - }).doOnError(ex -> { - long elapsedMillis = System.currentTimeMillis() - start; - this.doOnBodyError(ex, elapsedMillis); - }); - - // fallback handler - InputConfig reqConfig = (InputConfig) config; - if (reqConfig.getFallback() != null) { - Map fallback = reqConfig.getFallback(); - String mode = fallback.get("mode"); - if (FALLBACK_MODE_STOP.equals(mode)) { - body = body.onErrorStop(); - } else if (FALLBACK_MODE_CONTINUE.equals(mode)) { - body = body.onErrorResume(ex -> { - return Mono.just(fallback.get("defaultResult")); - }); - } else { - body = body.onErrorStop(); - } - } - - return body.flatMap(item -> { - Map result = new HashMap(); - result.put("data", item); - result.put("request", this); - - long t3 = System.currentTimeMillis(); - this.doResponseMapping(config, inputContext, item); - inputContext.getStepContext().addElapsedTime( - stepResponse.getStepName() + "-" + this.name + "-ResponseMapping", System.currentTimeMillis() - t3); - - return Mono.just(result); - }); - } - - private void cleanup(RPCResponse clientResponse) { - - } - - protected Mono getClientSpecFromContext(InputConfig aConfig, InputContext inputContext) { - return null; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/RPCResponse.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/RPCResponse.java deleted file mode 100644 index 5c5f502..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/RPCResponse.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import org.springframework.util.MultiValueMap; - -import reactor.core.publisher.Mono; - -/** - * - * @author linwaiwai - * - */ -public class RPCResponse { - private MultiValueMap headers; - private Mono bodyMono; - - public MultiValueMap getHeaders() { - return headers; - } - - public void setHeaders(MultiValueMap headers) { - this.headers = headers; - } - - public Mono getBodyMono() { - return bodyMono; - } - - public void setBodyMono(Mono bodyMono) { - this.bodyMono = bodyMono; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/ScriptHelper.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/ScriptHelper.java deleted file mode 100644 index c247e05..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/ScriptHelper.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - -import javax.script.ScriptException; - -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.exception.RedirectException; -import com.fizzgate.exception.StopAndResponseException; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.util.JacksonUtils; -import com.fizzgate.util.Script; -import com.fizzgate.util.ScriptUtils; - -import org.springframework.util.StringUtils; - -/** - * - * @author Francis Dong - * - */ -public class ScriptHelper { - - private static final Logger LOGGER = LoggerFactory.getLogger(ScriptHelper.class); - - public static Object execute(Map scriptCfg, ONode ctxNode, StepContext stepContext) - throws ScriptException { - return execute(scriptCfg, ctxNode, stepContext, Object.class); - } - - @SuppressWarnings("unchecked") - public static T execute(Map scriptCfg, ONode ctxNode, StepContext stepContext, Class clazz) - throws ScriptException { - Script script = new Script(); - script.setType((String) scriptCfg.get("type")); - script.setSource((String) scriptCfg.get("source")); - if (!StringUtils.hasText(script.getType()) || !StringUtils.hasText(script.getSource())) { - return null; - } - - Map ctx = new HashMap<>(); - ctx.put("context", stepContext); - - Object rs = ScriptUtils.execute(script, ctx); - if (ScriptUtils.GROOVY.equals(script.getType())) { - return (T) handleStopResponse(stepContext, rs); - } else if (ScriptUtils.JAVA_SCRIPT.equals(script.getType())) { - if(rs != null) { - if(rs instanceof Collection || rs instanceof Map) { - return (T) rs; - }else { - String json = rs.toString(); - if(json.startsWith("[") && json.endsWith("]")) { - return JSON.parseArray(json).toJavaObject(clazz); - }else if(json.startsWith("{") && json.endsWith("}")) { - if(clazz.isAssignableFrom(Map.class)) { - return (T)handleStopResponse(stepContext, JSON.parseObject(json).toJavaObject(clazz)); - }else { - handleStopResponse(stepContext, JSON.parseObject(json).toJavaObject(Map.class)); - return JSON.parseObject(json).toJavaObject(clazz); - } - } - } - return (T) rs; - } - return null; - } else { - return (T) rs; - } - } - - public static Map executeScripts(ONode target, Map scriptRules, ONode ctxNode, - StepContext stepContext, boolean supportMultiLevels) { - return executeScripts(target, scriptRules, ctxNode, stepContext, Object.class, supportMultiLevels); - } - - @SuppressWarnings("unchecked") - public static Map executeScripts(ONode target, Map scriptRules, ONode ctxNode, - StepContext stepContext, Class clazz, boolean supportMultiLevels) { - if(target == null) { - target = ONode.load(new HashMap()); - } - if (scriptRules != null && !scriptRules.isEmpty()) { - // wildcard star entry - Object starValObj = null; - String starEntryKey = null; - for (Entry entry : scriptRules.entrySet()) { - Map scriptCfg = (Map) entry.getValue(); - try { - if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) { - starValObj = execute(scriptCfg, ctxNode, stepContext, clazz); - starEntryKey = entry.getKey(); - }else { - PathMapping.setByPath(target, entry.getKey(), execute(scriptCfg, ctxNode, stepContext, clazz), supportMultiLevels); - } - } catch (ScriptException e) { - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - if(starEntryKey != null) { - PathMapping.setByPath(target, starEntryKey, starValObj, supportMultiLevels); - } - } - return target.toObject(Map.class); - } - - public static Object handleStopResponse(StepContext stepContext, Object result) { - if(result instanceof Map) { - Map rs = (Map) result; - if (rs.containsKey(CommonConstants.STOP_AND_RESPONSE_KEY)) { - if (rs.get(CommonConstants.STOP_AND_RESPONSE_KEY) != null - && rs.get(CommonConstants.STOP_AND_RESPONSE_KEY) instanceof Boolean - && (Boolean) rs.get(CommonConstants.STOP_AND_RESPONSE_KEY)) { - rs.remove(CommonConstants.STOP_AND_RESPONSE_KEY); - - // redirect - if(rs.get(CommonConstants.REDIRECT_URL_KEY) != null) { - throw new RedirectException("stop and redirect", String.valueOf(rs.get(CommonConstants.REDIRECT_URL_KEY))); - } - - // 测试模式返回StepContext - if (stepContext.returnContext()) { - rs.put(stepContext.CONTEXT_FIELD, stepContext); - } - - // exception - throw new StopAndResponseException("stop and response", JSON.toJSONString(rs)); - } else { - rs.remove(CommonConstants.STOP_AND_RESPONSE_KEY); - } - } - } - return result; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInput.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInput.java deleted file mode 100644 index 8b772cc..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInput.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.dubbo; - -import org.apache.logging.log4j.ThreadContext; -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.util.CollectionUtils; - -import com.fizzgate.FizzAppContext; -import com.fizzgate.config.SystemConfig; -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.input.*; -import com.fizzgate.proxy.dubbo.ApacheDubboGenericService; -import com.fizzgate.proxy.dubbo.DubboInterfaceDeclaration; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; - -import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; - -import javax.script.ScriptException; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class DubboInput extends RPCInput { - static public InputType TYPE = new InputType("DUBBO"); - private static final Logger LOGGER = LoggerFactory.getLogger(DubboInput.class); - - @SuppressWarnings("unchecked") - @Override - protected Mono getClientSpecFromContext(InputConfig aConfig, InputContext inputContext) { - DubboInputConfig config = (DubboInputConfig) aConfig; - - int timeout = config.getTimeout() < 1 ? 10000 : config.getTimeout() > 30000 ? 30000 : config.getTimeout(); - long numRetries = config.getNumRetries() > 0 ? config.getNumRetries() : 0; - long retryInterval = config.getRetryInterval() > 0 ? config.getRetryInterval() : 0; - Map attachments = (Map) request.get("attachments"); - ConfigurableApplicationContext applicationContext = this.getCurrentApplicationContext(); - Map body = (Map) request.get("body"); - - ApacheDubboGenericService proxy = applicationContext.getBean(ApacheDubboGenericService.class); - DubboInterfaceDeclaration declaration = new DubboInterfaceDeclaration(); - declaration.setServiceName(config.getServiceName()); - declaration.setVersion(config.getVersion()); - declaration.setGroup(config.getGroup()); - declaration.setMethod(config.getMethod()); - declaration.setParameterTypes(config.getParamTypes()); - declaration.setTimeout(timeout); - HashMap contextAttachment = null; - if (attachments == null) { - contextAttachment = new HashMap(); - } else { - contextAttachment = new HashMap(attachments); - } - if (inputContext.getStepContext() != null && inputContext.getStepContext().getTraceId() != null) { - if (FizzAppContext.appContext == null) { - contextAttachment.put(CommonConstants.HEADER_TRACE_ID, inputContext.getStepContext().getTraceId()); - } else { - SystemConfig systemConfig = FizzAppContext.appContext.getBean(SystemConfig.class); - contextAttachment.put(systemConfig.fizzTraceIdHeader(), inputContext.getStepContext().getTraceId()); - } - } - - HashMap contextAttachment2 = contextAttachment; - Mono proxyResponse = Mono.just("").flatMap(s -> { - return proxy.send(body, declaration, contextAttachment2); - }); - return proxyResponse.retryWhen(Retry.fixedDelay(numRetries, Duration.ofMillis(retryInterval)) - .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> { - throw new FizzRuntimeException("External Dubbo Service failed to process after max retries"); - })).flatMap(cr -> { - DubboRPCResponse response = new DubboRPCResponse(); - response.setBodyMono(Mono.just(cr)); - return Mono.just(response); - }); - } - - protected void doRequestMapping(InputConfig aConfig, InputContext inputContext) { - DubboInputConfig config = (DubboInputConfig) aConfig; - - // 把请求信息放入stepContext - Map group = new HashMap<>(); - group.put("request", request); - group.put("response", response); - this.stepResponse.addRequest(name, group); - - request.put("serviceName", config.getServiceName()); - request.put("version", config.getVersion()); - request.put("group", config.getGroup()); - request.put("method", config.getMethod()); - request.put("paramTypes", config.getParamTypes()); - - // 数据转换 - if (inputContext != null && inputContext.getStepContext() != null) { - StepContext stepContext = inputContext.getStepContext(); - Map dataMapping = this.getConfig().getDataMapping(); - if (dataMapping != null) { - Map requestMapping = (Map) dataMapping.get("request"); - if (!CollectionUtils.isEmpty(requestMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - - // attachments - Map attachments = PathMapping.transform(ctxNode, stepContext, - (Map) requestMapping.get("fixedHeaders"), - (Map) requestMapping.get("headers")); - if (attachments.containsKey(CommonConstants.WILDCARD_TILDE) - && attachments.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { - request.put("attachments", attachments.get(CommonConstants.WILDCARD_TILDE)); - } else { - request.put("attachments", attachments); - } - - // body - Map body = PathMapping.transform(ctxNode, stepContext, - (Map) requestMapping.get("fixedBody"), - (Map) requestMapping.get("body")); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - request.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (requestMapping.get("script") != null) { - Map scriptCfg = (Map) requestMapping.get("script"); - try { - Object reqBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if (reqBody != null) { - body.putAll((Map) reqBody); - } - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - request.put("body", body); - } - } - } - } - } - - protected void doOnResponseSuccess(RPCResponse cr, long elapsedMillis) { - inputContext.getStepContext().addElapsedTime(this.getApiName(), elapsedMillis); - } - - protected Mono bodyToMono(RPCResponse cr) { - return cr.getBodyMono(); - } - - protected void doOnBodyError(Throwable ex, long elapsedMillis) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("failed to call {}", this.getApiName(), ex); - inputContext.getStepContext().addElapsedTime(this.getApiName() + " failed ", elapsedMillis); - } - - protected void doOnBodySuccess(Object resp, long elapsedMillis) { - - } - - protected void doResponseMapping(InputConfig aConfig, InputContext inputContext, Object responseBody) { - DubboInputConfig config = (DubboInputConfig) aConfig; - response.put("body", responseBody); - - // 数据转换 - if (inputContext != null && inputContext.getStepContext() != null) { - StepContext stepContext = inputContext.getStepContext(); - Map dataMapping = this.getConfig().getDataMapping(); - if (dataMapping != null) { - Map responseMapping = (Map) dataMapping.get("response"); - if (!CollectionUtils.isEmpty(responseMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - - // body - Map fixedBody = (Map) responseMapping.get("fixedBody"); - Map bodyMapping = (Map) responseMapping.get("body"); - Map scriptCfg = (Map) responseMapping.get("script"); - if ((fixedBody != null && !fixedBody.isEmpty()) || (bodyMapping != null && !bodyMapping.isEmpty()) - || (scriptCfg != null && scriptCfg.get("type") != null - && scriptCfg.get("source") != null)) { - // body - Map body = new HashMap<>(); - body.putAll(PathMapping.transform(ctxNode, stepContext, fixedBody, bodyMapping)); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - response.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (scriptCfg != null && scriptCfg.get("type") != null && scriptCfg.get("source") != null) { - try { - Object respBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if (respBody != null) { - body.putAll((Map) respBody); - } - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - response.put("body", body); - } - } - } - } else { - response.put("body", responseBody); - } - } - } - - public static Class inputConfigClass() { - return DubboInputConfig.class; - } - - private String getApiName() { - return prefix + " - " + request.get("serviceName") + " - " + request.get("method"); - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInputConfig.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInputConfig.java deleted file mode 100644 index d3a3b7b..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboInputConfig.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.input.extension.dubbo; - -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.input.InputConfig; - -/** -* -* @author linwaiwai -* @author Francis Dong -* -*/ -public class DubboInputConfig extends InputConfig { - private String serviceName; - private String version; - private String group; - private String method; - private String paramTypes; - private int timeout; - private long numRetries; - /** - * retry interval in millisecond - */ - private long retryInterval; - - public DubboInputConfig(Map configMap) { - super(configMap); - } - - public void parse() { - String serviceName = (String) configMap.get("serviceName"); - if (StringUtils.isBlank(serviceName)) { - throw new FizzRuntimeException("service name can not be blank"); - } - setServiceName(serviceName); - - String version = (String) configMap.get("version"); - if (!StringUtils.isBlank(version)) { - setVersion(version); - } - - String group = (String) configMap.get("group"); - if (!StringUtils.isBlank(group)) { - setGroup(group); - } - - String method = (String) configMap.get("method"); - if (StringUtils.isBlank(method)) { - throw new FizzRuntimeException("method can not be blank"); - } - setMethod(method); - String paramTypes = (String) configMap.get("paramTypes"); - if (!StringUtils.isBlank(paramTypes)) { - setParamTypes(paramTypes); - } - - if (configMap.get("timeout") != null && StringUtils.isNotBlank(configMap.get("timeout").toString())) { - try { - setTimeout(Integer.valueOf(configMap.get("timeout").toString())); - } catch (Exception e) { - throw new RuntimeException("invalid timeout: " + configMap.get("timeout").toString() + " " + e.getMessage(), e); - } - } - if (configMap.get("numRetries") != null && StringUtils.isNotBlank(configMap.get("numRetries").toString())) { - try { - numRetries = Long.valueOf(configMap.get("numRetries").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid numRetries: " + configMap.get("numRetries").toString() + " " + e.getMessage(), e); - } - } - if (configMap.get("retryInterval") != null && StringUtils.isNotBlank(configMap.get("retryInterval").toString())) { - try { - retryInterval = Long.valueOf(configMap.get("retryInterval").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid retryInterval: " + configMap.get("retryInterval").toString() + " " + e.getMessage(), e); - } - } - } - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getParamTypes() { - return paramTypes; - } - - public void setParamTypes(String paramTypes) { - this.paramTypes = paramTypes; - } - - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - - public void setMethod(String method) { - this.method = method; - } - - public String getServiceName() { - return serviceName; - } - - public String getMethod() { - return method; - } - - public int getTimeout() { - return timeout; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public long getNumRetries() { - return numRetries; - } - - public void setNumRetries(long numRetries) { - this.numRetries = numRetries; - } - - public long getRetryInterval() { - return retryInterval; - } - - public void setRetryInterval(long retryInterval) { - this.retryInterval = retryInterval; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboRPCResponse.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboRPCResponse.java deleted file mode 100644 index a1f5a1a..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/dubbo/DubboRPCResponse.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.dubbo; - -import com.fizzgate.fizz.input.RPCResponse; - -/** -* -* @author linwaiwai -* -*/ -public class DubboRPCResponse extends RPCResponse { -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GRPCResponse.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GRPCResponse.java deleted file mode 100644 index a58d703..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GRPCResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.grpc; - -import org.springframework.http.HttpStatus; - -import com.fizzgate.fizz.input.RPCResponse; - -/** - * - * @author linwaiwai - * - */ -public class GRPCResponse extends RPCResponse { - private HttpStatus statusCode; - public void setStatus(HttpStatus statusCode) { - this.statusCode = statusCode; - } - - public HttpStatus getStatusCode() { - return statusCode; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInput.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInput.java deleted file mode 100644 index 9af9e11..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInput.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.grpc; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.FizzAppContext; -import com.fizzgate.config.SystemConfig; -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.input.*; -import com.fizzgate.proxy.grpc.GrpcGenericService; -import com.fizzgate.proxy.grpc.GrpcInstanceService; -import com.fizzgate.proxy.grpc.GrpcInterfaceDeclaration; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; - -import org.apache.logging.log4j.ThreadContext; -import org.noear.snack.ONode; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.util.CollectionUtils; -import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; - -import javax.script.ScriptException; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class GrpcInput extends RPCInput implements IInput { - static public InputType TYPE = new InputType("GRPC"); - - @SuppressWarnings("unchecked") - @Override - protected Mono getClientSpecFromContext(InputConfig aConfig, InputContext inputContext) { - GrpcInputConfig config = (GrpcInputConfig) aConfig; - - int timeout = config.getTimeout() < 1 ? 10000 : config.getTimeout() > 30000 ? 30000 : config.getTimeout(); - long numRetries = config.getNumRetries() > 0 ? config.getNumRetries() : 0; - long retryInterval = config.getRetryInterval() > 0 ? config.getRetryInterval() : 0; - Map attachments = (Map) request.get("attachments"); - ConfigurableApplicationContext applicationContext = this.getCurrentApplicationContext(); - Map body = (Map) request.get("body"); - String endpoint = (String) request.get("endpoint"); - - GrpcGenericService proxy = applicationContext.getBean(GrpcGenericService.class); - GrpcInterfaceDeclaration declaration = new GrpcInterfaceDeclaration(); - declaration.setEndpoint(endpoint); - declaration.setServiceName(config.getServiceName()); - declaration.setMethod(config.getMethod()); - declaration.setTimeout(timeout); - HashMap contextAttachment = null; - if (attachments == null) { - contextAttachment = new HashMap(); - } else { - contextAttachment = new HashMap(attachments); - } - if (inputContext.getStepContext() != null && inputContext.getStepContext().getTraceId() != null) { - if (FizzAppContext.appContext == null) { - contextAttachment.put(CommonConstants.HEADER_TRACE_ID, inputContext.getStepContext().getTraceId()); - } else { - SystemConfig systemConfig = FizzAppContext.appContext.getBean(SystemConfig.class); - contextAttachment.put(systemConfig.fizzTraceIdHeader(), inputContext.getStepContext().getTraceId()); - } - } - - HashMap contextAttachment2 = contextAttachment; - Mono proxyResponse = Mono.just("").flatMap(s -> { - return proxy.send(JSON.toJSONString(body), declaration, contextAttachment2); - }); - return proxyResponse.retryWhen(Retry.fixedDelay(numRetries, Duration.ofMillis(retryInterval)) - .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> { - throw new FizzRuntimeException("External gRPC Service failed to process after max retries"); - })).flatMap(cr -> { - GRPCResponse response = new GRPCResponse(); - response.setBodyMono(Mono.just(cr)); - return Mono.just(response); - }); - } - - @SuppressWarnings("unchecked") - protected void doRequestMapping(InputConfig aConfig, InputContext inputContext) { - GrpcInputConfig config = (GrpcInputConfig) aConfig; - - // 把请求信息放入stepContext - Map group = new HashMap<>(); - group.put("request", request); - group.put("response", response); - this.stepResponse.addRequest(name, group); - - request.put("serviceName", config.getServiceName()); - request.put("method", config.getMethod()); - GrpcInstanceService grpcInstanceService = this.getCurrentApplicationContext() - .getBean(GrpcInstanceService.class); - request.put("endpoint", grpcInstanceService.getInstance(config.getServiceName())); - - // 数据转换 - if (inputContext != null && inputContext.getStepContext() != null) { - StepContext stepContext = inputContext.getStepContext(); - Map dataMapping = this.getConfig().getDataMapping(); - if (dataMapping != null) { - Map requestMapping = (Map) dataMapping.get("request"); - if (!CollectionUtils.isEmpty(requestMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - - // attachments - Map attachments = PathMapping.transform(ctxNode, stepContext, - (Map) requestMapping.get("fixedHeaders"), - (Map) requestMapping.get("headers")); - if (attachments.containsKey(CommonConstants.WILDCARD_TILDE) - && attachments.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { - request.put("attachments", attachments.get(CommonConstants.WILDCARD_TILDE)); - } else { - request.put("attachments", attachments); - } - - // body - Map body = PathMapping.transform(ctxNode, stepContext, - (Map) requestMapping.get("fixedBody"), - (Map) requestMapping.get("body")); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - request.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (requestMapping.get("script") != null) { - Map scriptCfg = (Map) requestMapping.get("script"); - try { - Object reqBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if (reqBody != null) { - body.putAll((Map) reqBody); - } - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - request.put("body", body); - } - } - } - } - } - - protected void doOnResponseSuccess(RPCResponse cr, long elapsedMillis) { - inputContext.getStepContext().addElapsedTime(this.getApiName(), elapsedMillis); - } - - protected Mono bodyToMono(RPCResponse cr) { - return cr.getBodyMono(); - } - - protected void doOnBodyError(Throwable ex, long elapsedMillis) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("failed to call {}", this.getApiName(), ex); - inputContext.getStepContext().addElapsedTime(this.getApiName() + " failed ", elapsedMillis); - } - - protected void doOnBodySuccess(Object resp, long elapsedMillis) { - - } - - @SuppressWarnings("unchecked") - protected void doResponseMapping(InputConfig aConfig, InputContext inputContext, Object responseBody) { -// GrpcInputConfig config = (GrpcInputConfig) aConfig; - response.put("body", responseBody); - - // 数据转换 - if (inputContext != null && inputContext.getStepContext() != null) { - StepContext stepContext = inputContext.getStepContext(); - Map dataMapping = this.getConfig().getDataMapping(); - if (dataMapping != null) { - Map responseMapping = (Map) dataMapping.get("response"); - if (!CollectionUtils.isEmpty(responseMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - - // body - Map fixedBody = (Map) responseMapping.get("fixedBody"); - Map bodyMapping = (Map) responseMapping.get("body"); - Map scriptCfg = (Map) responseMapping.get("script"); - if ((fixedBody != null && !fixedBody.isEmpty()) || (bodyMapping != null && !bodyMapping.isEmpty()) - || (scriptCfg != null && scriptCfg.get("type") != null - && scriptCfg.get("source") != null)) { - // body - Map body = new HashMap<>(); - body.putAll(PathMapping.transform(ctxNode, stepContext, fixedBody, bodyMapping)); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - response.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (scriptCfg != null && scriptCfg.get("type") != null && scriptCfg.get("source") != null) { - try { - Object respBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if (respBody != null) { - body.putAll((Map) respBody); - } - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), - e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - response.put("body", body); - } - } - } - } else { - response.put("body", responseBody); - } - } - } - - private String getApiName() { - return prefix + " - " + request.get("serviceName") + " - " + request.get("method"); - } - - public static Class inputConfigClass() { - return GrpcInputConfig.class; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInputConfig.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInputConfig.java deleted file mode 100644 index fda7382..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/grpc/GrpcInputConfig.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.grpc; - -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; - -import com.fizzgate.fizz.exception.FizzRuntimeException; -import com.fizzgate.fizz.input.InputConfig; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class GrpcInputConfig extends InputConfig { - - private int timeout; - private String serviceName; - private String method; - private long numRetries; - /** - * retry interval in millisecond - */ - private long retryInterval; - - public GrpcInputConfig(Map configMap) { - super(configMap); - } - - public void parse() { - String serviceName = (String) configMap.get("serviceName"); - if (StringUtils.isBlank(serviceName)) { - throw new FizzRuntimeException("service name can not be blank"); - } - setServiceName(serviceName); - - String method = (String) configMap.get("method"); - if (StringUtils.isBlank(method)) { - throw new FizzRuntimeException("method can not be blank"); - } - setMethod(method); - - if (configMap.get("timeout") != null && StringUtils.isNotBlank(configMap.get("timeout").toString())) { - try { - setTimeout(Integer.valueOf(configMap.get("timeout").toString())); - } catch (Exception e) { - throw new RuntimeException("invalid timeout: " + configMap.get("timeout").toString() + " " + e.getMessage(), e); - } - } - if (configMap.get("numRetries") != null && StringUtils.isNotBlank(configMap.get("numRetries").toString())) { - try { - numRetries = Long.valueOf(configMap.get("numRetries").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid numRetries: " + configMap.get("numRetries").toString() + " " + e.getMessage(), e); - } - } - if (configMap.get("retryInterval") != null && StringUtils.isNotBlank(configMap.get("retryInterval").toString())) { - try { - retryInterval = Long.valueOf(configMap.get("retryInterval").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid retryInterval: " + configMap.get("retryInterval").toString() + " " + e.getMessage(), e); - } - } - } - - public int getTimeout() { - return timeout; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public String getServiceName() { - return serviceName; - } - - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - - public String getMethod() { - return method; - } - - public void setMethod(String method) { - this.method = method; - } - - public long getNumRetries() { - return numRetries; - } - - public void setNumRetries(long numRetries) { - this.numRetries = numRetries; - } - - public long getRetryInterval() { - return retryInterval; - } - - public void setRetryInterval(long retryInterval) { - this.retryInterval = retryInterval; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInput.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInput.java deleted file mode 100644 index bd6adc7..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInput.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.request; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.config.SystemConfig; -import com.fizzgate.constants.CommonConstants; -import com.fizzgate.exception.ExecuteScriptException; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.StepResponse; -import com.fizzgate.fizz.input.*; -import com.fizzgate.proxy.FizzWebClient; -import com.fizzgate.proxy.http.HttpInstanceService; -import com.fizzgate.service_registry.RegistryCenterService; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; -import com.fizzgate.util.MapUtil; -import com.fizzgate.util.TypeUtils; -import com.fizzgate.xml.JsonToXml; -import com.fizzgate.xml.XmlToJson; -import com.fizzgate.xml.XmlToJson.Builder; - -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.ThreadContext; -import org.noear.snack.ONode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; -import reactor.core.publisher.Mono; - -import javax.script.ScriptException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -@SuppressWarnings("unchecked") -public class RequestInput extends RPCInput implements IInput{ - - private static final Logger LOGGER = LoggerFactory.getLogger(RequestInput.class); - static public InputType TYPE = new InputType("REQUEST"); - private InputType type; - protected Map dataMapping; - - private static final String CONTENT_TYPE_JSON = "application/json"; - private static final String CONTENT_TYPE_XML = "application/xml"; - private static final String CONTENT_TYPE_TEXT_XML = "text/xml"; - private static final String CONTENT_TYPE_JS = "application/javascript"; - private static final String CONTENT_TYPE_HTML = "text/html"; - private static final String CONTENT_TYPE_TEXT = "text/plain"; - private static final String CONTENT_TYPE_AUTO = "auto"; - private static final String CONTENT_TYPE_MULTIPART_FORM_DATA = "multipart/form-data"; - private static final String CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"; - - private static final String CONTENT_TYPE = "content-type"; - - private static final Integer SERVICE_TYPE_DISCOVERY = 1; - private static final Integer SERVICE_TYPE_HTTP = 2; - - private String respContentType; - private String reqContentType; - - private String[] xmlArrPaths; - - private static Pattern PATH_VAR_PATTERN = Pattern.compile("(\\{)([^/]*)(\\})"); - - public InputType getType() { - return type; - } - - public void setType(InputType typeEnum) { - this.type = typeEnum; - } - - public Map getDataMapping() { - return dataMapping; - } - - public void setDataMapping(Map dataMapping) { - this.dataMapping = dataMapping; - } - - protected void doRequestMapping(InputConfig aConfig, InputContext inputContext) { - RequestInputConfig config = (RequestInputConfig) aConfig; - - Map params = new HashMap<>(); - synchronized (inputContext.getStepContext()) { - // 把请求信息放入stepContext - Map group = new HashMap<>(); - group.put("request", request); - group.put("response", response); - this.stepResponse.addRequest(name, group); - - HttpMethod method = HttpMethod.valueOf(config.getMethod().toUpperCase()); - request.put("method", method); - - params.putAll(MapUtil.toHashMap(config.getQueryParams())); - request.put("params", params); - } - - ONode ctxNode = null; - // 数据转换 - if (inputContext != null && inputContext.getStepContext() != null) { - StepContext stepContext = inputContext.getStepContext(); - ctxNode = PathMapping.toONode(stepContext); - synchronized (stepContext) { - Map dataMapping = this.getConfig().getDataMapping(); - if (dataMapping != null) { - Map requestMapping = (Map) dataMapping.get("request"); - if (!CollectionUtils.isEmpty(requestMapping)) { - reqContentType = (String) requestMapping.get("contentType"); - - // headers - Map headers = PathMapping.transform(ctxNode, stepContext, - MapUtil.upperCaseKey(MapUtil.list2Map(requestMapping.get("fixedHeaders"))), - MapUtil.upperCaseKey(MapUtil.list2Map(requestMapping.get("headers"))), false); - if (headers.containsKey(CommonConstants.WILDCARD_TILDE) - && headers.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { - request.put("headers", headers.get(CommonConstants.WILDCARD_TILDE)); - } else { - request.put("headers", headers); - } - - // params - params.putAll(PathMapping.transform(ctxNode, stepContext, - MapUtil.list2Map(requestMapping.get("fixedParams")), - MapUtil.list2Map(requestMapping.get("params")), false)); - if (params.containsKey(CommonConstants.WILDCARD_TILDE) - && params.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { - request.put("params", params.get(CommonConstants.WILDCARD_TILDE)); - } else { - request.put("params", params); - } - - // body - boolean supportMultiLevels = true; - if (CONTENT_TYPE_MULTIPART_FORM_DATA.equals(reqContentType) || - CONTENT_TYPE_FORM_URLENCODED.equals(reqContentType)) { - supportMultiLevels = false; - } - Map body = PathMapping.transform(ctxNode, stepContext, - MapUtil.list2Map(requestMapping.get("fixedBody")), - MapUtil.list2Map(requestMapping.get("body")), supportMultiLevels); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - request.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (requestMapping.get("script") != null) { - Map scriptCfg = (Map) requestMapping.get("script"); - try { - Object reqBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if (reqBody != null) { - body.putAll((Map) reqBody); - } - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - request.put("body", body); - } - } - } - } - } - - if (config.isNewVersion()) { - String host = config.getServiceName(); - - if (SERVICE_TYPE_HTTP.equals(config.getServiceType().intValue())) { - HttpInstanceService httpInstanceService = this.getCurrentApplicationContext() - .getBean(HttpInstanceService.class); - String instance = httpInstanceService.getInstance(config.getServiceName()); - if (instance != null) { - host = instance; - } - } else if (SERVICE_TYPE_DISCOVERY.equals(config.getServiceType())) { - if (StringUtils.isNotBlank(config.getRegistryName())) { - // support choosing registry center - host = RegistryCenterService.getServiceNameSpace(config.getRegistryName(), host); - } - } - StringBuffer sb = new StringBuffer(); - sb.append(config.getProtocol()).append("://").append(host) - .append(config.getPath().startsWith("/") ? "" : "/").append(setPathVariable(ctxNode, config.getPath())); - - UriComponents uriComponents = UriComponentsBuilder.fromUriString(sb.toString()) - .queryParams(MapUtil.toMultiValueMap(params)).build(); - - synchronized (inputContext.getStepContext()) { - request.put("url", uriComponents.toUriString()); - } - } else { - UriComponents uriComponents = UriComponentsBuilder.fromUriString(config.getBaseUrl() + setPathVariable(ctxNode, config.getPath())) - .queryParams(MapUtil.toMultiValueMap(params)).build(); - synchronized (inputContext.getStepContext()) { - request.put("url", uriComponents.toUriString()); - } - } - } - - private String setPathVariable(ONode ctxNode, String path) { - if (ctxNode == null || StringUtils.isBlank(path)) { - return path; - } - String[] paths = path.split("/"); - for (int i = 0; i < paths.length; i++) { - Matcher matcher = PATH_VAR_PATTERN.matcher(paths[i]); - if (matcher.find()) { - String jsonPath = matcher.group(2); - Object val = PathMapping.getValueByPath(ctxNode, jsonPath); - if (val != null && !(val instanceof Map) && !(val instanceof List)) { - paths[i] = matcher.replaceAll(String.valueOf(val)); - } - } - } - return String.join("/", paths); - } - - @Override - public void doResponseMapping(InputConfig aConfig, InputContext inputContext, Object responseBody) { - - RequestInputConfig config = (RequestInputConfig) aConfig; - String cfgContentType = null; - Map dataMapping = config.getDataMapping(); - Map responseMapping = null; - if (dataMapping != null) { - responseMapping = (Map) dataMapping.get("response"); - if (!CollectionUtils.isEmpty(responseMapping)) { - cfgContentType = (String) responseMapping.get("contentType"); - String paths = (String) responseMapping.get("xmlArrPaths"); - if (StringUtils.isNotBlank(paths)) { - xmlArrPaths = paths.split(","); - } - } - } - - String ct = null; - if (cfgContentType == null || CONTENT_TYPE_AUTO.equals(cfgContentType)) { - ct = this.respContentType; - } else { - ct = cfgContentType; - } - if (StringUtils.isBlank(ct)) { - ct = CONTENT_TYPE_JSON; - } - - synchronized (inputContext.getStepContext()) { - response.put("body", this.parseBody(ct, (String)responseBody)); - } - - // 数据转换 - if (inputContext != null && inputContext.getStepContext() != null) { - StepContext stepContext = inputContext.getStepContext(); - if (!CollectionUtils.isEmpty(responseMapping)) { - ONode ctxNode = PathMapping.toONode(stepContext); - synchronized (stepContext) { - // headers - Map fixedHeaders = MapUtil.upperCaseKey((Map) responseMapping.get("fixedHeaders")); - Map headerMapping = MapUtil.upperCaseKey((Map) responseMapping.get("headers")); - if ((fixedHeaders != null && !fixedHeaders.isEmpty()) - || (headerMapping != null && !headerMapping.isEmpty())) { - Map headers = new HashMap<>(); - headers.putAll(PathMapping.transform(ctxNode, stepContext, fixedHeaders, headerMapping, false)); - if (headers.containsKey(CommonConstants.WILDCARD_TILDE) - && headers.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { - response.put("headers", headers.get(CommonConstants.WILDCARD_TILDE)); - } else { - response.put("headers", headers); - } - } - - // body - Map fixedBody = (Map) responseMapping.get("fixedBody"); - Map bodyMapping = (Map) responseMapping.get("body"); - Map scriptCfg = (Map) responseMapping.get("script"); - if ((fixedBody != null && !fixedBody.isEmpty()) || (bodyMapping != null && !bodyMapping.isEmpty()) - || (scriptCfg != null && scriptCfg.get("type") != null - && scriptCfg.get("source") != null)) { - // body - Map body = new HashMap<>(); - body.putAll(PathMapping.transform(ctxNode, stepContext, fixedBody, bodyMapping)); - if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { - response.put("body", body.get(CommonConstants.WILDCARD_TILDE)); - } else { - // script - if (scriptCfg != null && scriptCfg.get("type") != null && scriptCfg.get("source") != null) { - try { - Object respBody = ScriptHelper.execute(scriptCfg, ctxNode, stepContext); - if (respBody != null) { - body.putAll((Map) respBody); - } - } catch (ScriptException e) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("execute script failed, {}", JacksonUtils.writeValueAsString(scriptCfg), e); - throw new ExecuteScriptException(e, stepContext, scriptCfg); - } - } - response.put("body", body); - } - } - } - } - } - } - - @Override - protected Mono getClientSpecFromContext(InputConfig aConfig, InputContext inputContext) { - RequestInputConfig config = (RequestInputConfig) aConfig; - - int timeout = config.getTimeout() < 1 ? 10000 : config.getTimeout() > 30000 ? 30000 : config.getTimeout(); - long numRetries = config.getNumRetries() > 0 ? config.getNumRetries() : 0; - long retryInterval = config.getRetryInterval() > 0 ? config.getRetryInterval() : 0; - - HttpMethod method = HttpMethod.valueOf(config.getMethod()); - String url = (String) request.get("url"); - - Map hds = (Map) request.get("headers"); - if (hds == null) { - hds = new HashMap<>(); - } - HttpHeaders headers = MapUtil.toHttpHeaders(hds); - - // default content-type - if (!headers.containsKey(CommonConstants.HEADER_CONTENT_TYPE)) { - if (CONTENT_TYPE_XML.equals(reqContentType) || CONTENT_TYPE_TEXT_XML.equals(reqContentType)) { - headers.add(CommonConstants.HEADER_CONTENT_TYPE, CONTENT_TYPE_XML); - } else if (CONTENT_TYPE_MULTIPART_FORM_DATA.equals(reqContentType)) { - headers.add(CommonConstants.HEADER_CONTENT_TYPE, CONTENT_TYPE_MULTIPART_FORM_DATA); - } else if (CONTENT_TYPE_FORM_URLENCODED.equals(reqContentType)) { - headers.add(CommonConstants.HEADER_CONTENT_TYPE, CONTENT_TYPE_FORM_URLENCODED); - } else { - headers.add(CommonConstants.HEADER_CONTENT_TYPE, CommonConstants.CONTENT_TYPE_JSON); - } - } - - // add default headers - SystemConfig systemConfig = this.getCurrentApplicationContext().getBean(SystemConfig.class); - for (String hdr : systemConfig.getProxySetHeaders()) { - if(inputContext.getStepContext().getInputReqHeader(hdr) != null && !headers.containsKey(hdr)) { - Object o = inputContext.getStepContext().getInputReqHeader(hdr); - if (o instanceof String) { - headers.add(hdr, (String) o); - } else if (o instanceof List){ - List list = (List) o; - List vals = new ArrayList<>(); - for (Object item : list) { - if (item != null) { - vals.add(item.toString()); - } - } - headers.addAll(hdr, vals); - } - } - } - - headers.remove(CommonConstants.HEADER_CONTENT_LENGTH); - headers.add(systemConfig.fizzTraceIdHeader(), inputContext.getStepContext().getTraceId()); - synchronized (inputContext.getStepContext()) { - request.put("headers", MapUtil.headerToHashMap(headers)); - } - - Object body = null; - if (CONTENT_TYPE_XML.equals(reqContentType) || CONTENT_TYPE_TEXT_XML.equals(reqContentType)) { - // convert JSON to XML if it is XML content type - synchronized (inputContext.getStepContext()) { - request.put("jsonBody", request.get("body")); - } - String jsonStr = null; - if (TypeUtils.isBasicType(request.get("body"))) { - jsonStr = request.get("body").toString(); - } else { - jsonStr = JSON.toJSONString(request.get("body")); - } - LOGGER.info("jsonBody={}", jsonStr); - if (jsonStr.startsWith("{") || jsonStr.startsWith("[")) { - JsonToXml jsonToXml = new JsonToXml.Builder(jsonStr).build(); - body = jsonToXml.toString(); - } else { - body = jsonStr; - } - synchronized (inputContext.getStepContext()) { - request.put("body", body); - } - LOGGER.info("body={}", body); - LOGGER.info("headers={}", JSON.toJSONString(headers)); - } else if (CONTENT_TYPE_MULTIPART_FORM_DATA.equals(reqContentType)) { - MultiValueMap mpDataMap = MapUtil - .toMultipartDataMap((Map) request.get("body")); - MapUtil.replaceWithFilePart(mpDataMap, CommonConstants.FILE_KEY_PREFIX, - inputContext.getStepContext().getFilePartMap()); - body = BodyInserters.fromMultipartData(mpDataMap); - } else if (CONTENT_TYPE_FORM_URLENCODED.equals(reqContentType)) { - body = BodyInserters.fromFormData(MapUtil.toMultiValueMap((Map) request.get("body"))); - } else { - if (TypeUtils.isBasicType(request.get("body"))) { - body = request.get("body").toString(); - } else { - body = JSON.toJSONString(request.get("body")); - } - } - - HttpMethod aggrMethod = HttpMethod.valueOf(inputContext.getStepContext().getInputReqAttr("method").toString()); - String aggrPath = (String)inputContext.getStepContext().getInputReqAttr("path"); - String aggrService = aggrPath.split("\\/")[2]; - - FizzWebClient client = this.getCurrentApplicationContext().getBean(FizzWebClient.class); - // Mono clientResponse = client.aggrSend(aggrService, aggrMethod, aggrPath, null, method, url, - // headers, body, (long)timeout); - - Mono clientResponse = client.send(inputContext.getStepContext().getTraceId(), method, url, - headers, body, (long)timeout, numRetries, retryInterval); - return clientResponse.flatMap(cr->{ - RequestRPCResponse response = new RequestRPCResponse(); - response.setHeaders(cr.headers().asHttpHeaders()); - response.setBodyMono(cr.bodyToMono(String.class)); - response.setStatus(cr.statusCode()); - return Mono.just(response); - }); - } - - private Map getResponses(Map stepContext2) { - // TODO Auto-generated method stub - return null; - } - - protected void doOnResponseSuccess(RPCResponse cr, long elapsedMillis) { - HttpHeaders httpHeaders = (HttpHeaders) cr.getHeaders(); - Map headers = new HashMap<>(); - httpHeaders.forEach((key, value) -> { - if (value.size() > 1) { - headers.put(key.toUpperCase(), value); - } else { - headers.put(key.toUpperCase(), httpHeaders.getFirst(key)); - } - }); - headers.put("ELAPSEDTIME", elapsedMillis + "ms"); - - RequestRPCResponse reqCr = (RequestRPCResponse) cr; - synchronized (inputContext.getStepContext()) { - if (reqCr.getStatusCode() != null) { - this.response.put("httpStatus", reqCr.getStatusCode().value()); - } - this.response.put("headers", headers); - this.respContentType = httpHeaders.getFirst(CONTENT_TYPE); - inputContext.getStepContext().addElapsedTime(prefix + request.get("url"), - elapsedMillis); - } - } - protected Mono bodyToMono(ClientResponse cr){ - return cr.bodyToMono(String.class); - } - - protected void doOnBodyError(Throwable ex, long elapsedMillis) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.warn("failed to call {}", request.get("url"), ex); - synchronized (inputContext.getStepContext()) { - inputContext.getStepContext().addElapsedTime( - stepResponse.getStepName() + "-" + "调用接口 failed " + request.get("url"), elapsedMillis); - } - } - - // Parse response body according to content-type header - public Object parseBody(String contentType, String responseBody) { - String[] cts = contentType.split(";"); - Object body = null; - for (int i = 0; i < cts.length; i++) { - String ct = cts[i].toLowerCase(); - switch (ct) { - case CONTENT_TYPE_JSON: - body = JSON.parse(responseBody); - break; - case CONTENT_TYPE_TEXT: - // parse text as json if start with "{" and end with "}" or start with "[" and - // end with "]" - if ((responseBody.startsWith("{") && responseBody.endsWith("}")) - || (responseBody.startsWith("[") && responseBody.endsWith("]"))) { - try { - body = JSON.parse(responseBody); - } catch (Exception e) { - body = responseBody; - } - } else { - body = responseBody; - } - break; - case CONTENT_TYPE_XML: - case CONTENT_TYPE_TEXT_XML: - Builder builder = new XmlToJson.Builder(responseBody); - if (this.xmlArrPaths != null && this.xmlArrPaths.length > 0) { - for (int j = 0; j < this.xmlArrPaths.length; j++) { - String p = this.xmlArrPaths[j]; - builder = builder.forceList(p); - } - } - body = builder.build().toJson().toMap(); - break; - case CONTENT_TYPE_HTML: - body = responseBody; - break; - case CONTENT_TYPE_JS: - body = responseBody; - break; - } - if (body != null) { - break; - } - } - if (body == null) { - body = responseBody; - } - return body; - } - - protected void doOnBodySuccess(Object resp, long elapsedMillis) { - if(inputContext.getStepContext().isDebug()) { - // LogService.setBizId(inputContext.getStepContext().getTraceId()); - ThreadContext.put(Consts.TRACE_ID, inputContext.getStepContext().getTraceId()); - LOGGER.info("{} 耗时:{}ms URL={}, reqHeader={} req={} resp={}", prefix, elapsedMillis, request.get("url"), - JSON.toJSONString(this.request.get("headers")), - JSON.toJSONString(this.request.get("body")), resp); - } - } - - @SuppressWarnings("unused") - private void cleanup(ClientResponse clientResponse) { - if (clientResponse != null) { - clientResponse.bodyToMono(Void.class).subscribe(); - } - } - - @SuppressWarnings("rawtypes") - public static Class inputConfigClass (){ - return RequestInputConfig.class; - } - -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInputConfig.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInputConfig.java deleted file mode 100644 index e51d918..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestInputConfig.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.request; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.web.util.UriComponentsBuilder; - -import com.fizzgate.fizz.input.InputConfig; - - - -/** - * - * @author linwaiwai - * @author Francis Dong - * - */ -public class RequestInputConfig extends InputConfig { - private URL url ; - private String method ; - private int timeout; - private String protocol; - /** - * Service Type, 1 service discovery, 2 HTTP service - */ - private Integer serviceType; - private String serviceName; - private String path; - private long numRetries; - /** - * retry interval in millisecond - */ - private long retryInterval; - - private String registryName; - - public RequestInputConfig(Map configBody) { - super(configBody); - - if (configBody.get("serviceType") != null && StringUtils.isNotBlank((String) configBody.get("protocol")) - && StringUtils.isNotBlank((String) configBody.get("serviceName")) - && StringUtils.isNotBlank((String) configBody.get("path"))) { - serviceType = Integer.valueOf(configBody.get("serviceType").toString()); - protocol = ((String) configBody.get("protocol")).toLowerCase(); - serviceName = (String) configBody.get("serviceName"); - registryName = (String)configBody.get("registryName"); - path = (String) configBody.get("path"); - } else { - String url = (String) configBody.get("url"); - if (StringUtils.isBlank(url)) { - throw new RuntimeException("Request URL can not be blank"); - } - setUrl(url); - } - - if (configBody.get("method") != null) { - setMethod(((String)configBody.get("method")).toUpperCase()); - } else { - setMethod("GET"); - } - if (configBody.get("timeout") != null && StringUtils.isNotBlank(configBody.get("timeout").toString())) { - try { - timeout = Integer.valueOf(configBody.get("timeout").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid timeout: " + configBody.get("timeout").toString() + " " + e.getMessage(), e); - } - } - if (configBody.get("numRetries") != null && StringUtils.isNotBlank(configBody.get("numRetries").toString())) { - try { - numRetries = Long.valueOf(configBody.get("numRetries").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid numRetries: " + configBody.get("numRetries").toString() + " " + e.getMessage(), e); - } - } - if (configBody.get("retryInterval") != null && StringUtils.isNotBlank(configBody.get("retryInterval").toString())) { - try { - retryInterval = Long.valueOf(configBody.get("retryInterval").toString()); - } catch (Exception e) { - throw new RuntimeException("invalid retryInterval: " + configBody.get("retryInterval").toString() + " " + e.getMessage(), e); - } - } - if (configBody.get("fallback") != null) { - Map fallback = (Map)configBody.get("fallback"); - setFallback(fallback); - } - if (configBody.get("condition") != null) { - setCondition((Map)configBody.get("condition")); - } - } - - public boolean isNewVersion() { - if (serviceType != null && StringUtils.isNotBlank(protocol) && StringUtils.isNotBlank(serviceName) - && StringUtils.isNotBlank(path)) { - return true; - } - return false; - } - - public String getQueryStr(){ - return url.getQuery(); - } - - public MultiValueMap getQueryParams(){ - if (isNewVersion()) { - return null; - } - MultiValueMap parameters = - UriComponentsBuilder.fromUriString(url.toString()).build().getQueryParams(); - return parameters; - } - - - public String getBaseUrl() { - return url.getProtocol()+ "://"+ url.getHost() + (url.getPort() == -1 ? "" : ":" + url.getPort()); - } - - public String getPath() { - if (isNewVersion()) { - return this.path; - } - return url.getPath(); - } - - public void setUrl(String string) { - try { - url = new URL(string); - } catch (MalformedURLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - public RequestInputConfig() { - super(null); - } - - public String getMethod() { - return method; - } - - public void setMethod(String method) { - this.method = method; - } - - public int getTimeout() { - return timeout; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } - - public Integer getServiceType() { - return serviceType; - } - - public void setServiceType(Integer serviceType) { - this.serviceType = serviceType; - } - - public String getServiceName() { - return serviceName; - } - - public void setServiceName(String serviceName) { - this.serviceName = serviceName; - } - - public void setPath(String path) { - this.path = path; - } - - public long getNumRetries() { - return numRetries; - } - - public void setNumRetries(long numRetries) { - this.numRetries = numRetries; - } - - public long getRetryInterval() { - return retryInterval; - } - - public void setRetryInterval(long retryInterval) { - this.retryInterval = retryInterval; - } - - public String getRegistryName() { - return registryName; - } - - public void setRegistryName(String registryName) { - this.registryName = registryName; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestRPCResponse.java b/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestRPCResponse.java deleted file mode 100644 index 8518f2c..0000000 --- a/fizz-core/src/main/java/com/fizzgate/fizz/input/extension/request/RequestRPCResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input.extension.request; - -import org.springframework.http.HttpStatus; - -import com.fizzgate.fizz.input.RPCResponse; - -/** - * - * @author linwaiwai - * - */ -public class RequestRPCResponse extends RPCResponse { - private HttpStatus statusCode; - public void setStatus(HttpStatus statusCode) { - this.statusCode = statusCode; - } - - public HttpStatus getStatusCode() { - return statusCode; - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/global_resource/GlobalResourceService.java b/fizz-core/src/main/java/com/fizzgate/global_resource/GlobalResourceService.java index c9751c6..aae885e 100644 --- a/fizz-core/src/main/java/com/fizzgate/global_resource/GlobalResourceService.java +++ b/fizz-core/src/main/java/com/fizzgate/global_resource/GlobalResourceService.java @@ -24,7 +24,6 @@ import org.springframework.data.redis.core.ReactiveStringRedisTemplate; import org.springframework.stereotype.Service; import com.fizzgate.config.AggregateRedisConfig; -import com.fizzgate.fizz.input.PathMapping; import com.fizzgate.util.JacksonUtils; import com.fizzgate.util.Result; @@ -69,10 +68,18 @@ public class GlobalResourceService { } private void updateResNode() { - resNode = PathMapping.toONode(objectMap); + resNode = this.toONode(objectMap); log.info("global resource node is updated, new keys: {}", objectMap.keySet()); } + private ONode toONode(Object obj) { + ONode o; + synchronized (obj) { + o = ONode.loadObj(obj); + } + return o; + } + private Result initGlobalResource() { Result result = Result.succ(); Flux> resources = rt.opsForHash().entries("fizz_global_resource"); diff --git a/fizz-core/src/main/java/com/fizzgate/listener/AggregateChannelListener.java b/fizz-core/src/main/java/com/fizzgate/listener/AggregateChannelListener.java deleted file mode 100644 index 59386fd..0000000 --- a/fizz-core/src/main/java/com/fizzgate/listener/AggregateChannelListener.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.listener; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.TypeReference; -import com.fizzgate.fizz.ConfigLoader; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.data.redis.connection.ReactiveSubscription; -import org.springframework.data.redis.listener.ChannelTopic; -import org.springframework.data.redis.listener.ReactiveRedisMessageListenerContainer; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import reactor.core.Disposable; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -import static com.fizzgate.config.AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER; - -import java.net.InetAddress; -import java.util.List; - -/** - * 聚合Channel监听器 - * @author zhongjie - */ -@Component -public class AggregateChannelListener { - public AggregateChannelListener(@Qualifier(AGGREGATE_REACTIVE_REDIS_MESSAGE_LISTENER_CONTAINER) - ReactiveRedisMessageListenerContainer reactiveRedisMessageListenerContainer, - ConfigLoader configLoader) { - this.reactiveRedisMessageListenerContainer = reactiveRedisMessageListenerContainer; - this.configLoader = configLoader; - } - - private static final Logger LOGGER = LoggerFactory.getLogger(AggregateChannelListener.class); - /** - * 聚合配置Channel名称,fizz-manager会往该Channel发送聚合变更信息 - */ - private static final String AGGREGATE_CHANNEL = "fizz_aggregate_channel"; - - private ReactiveRedisMessageListenerContainer reactiveRedisMessageListenerContainer; - private ConfigLoader configLoader; - private Disposable disposable; - - @PostConstruct - public void init() { - Flux> aggregateMessageFlux = - reactiveRedisMessageListenerContainer.receive(ChannelTopic.of(AGGREGATE_CHANNEL)); - disposable = aggregateMessageFlux.parallel().runOn(Schedulers.parallel()).subscribe(message -> { - String messageBody = message.getMessage(); - LOGGER.info("获取到[{}]消息[{}]", AGGREGATE_CHANNEL, messageBody); - try { - this.handleAggregateMessage(messageBody); - } catch (Exception e) { - LOGGER.warn(String.format("处理聚合推送数据异常[%s]", message), e); - } - }); - } - - @PreDestroy - public void destroy() { - if (disposable != null && !disposable.isDisposed()) { - disposable.dispose(); - } - } - - private static final String TYPE_PUBLISH = "publish "; - private static final String TYPE_ROLLBACK = "rollback "; - private static final String TYPE_TEST = "test "; - private static final String TYPE_DELETE = "delete "; - private static final String TYPE_REFRESH = "refresh "; - private void handleAggregateMessage(String message) throws Exception { - if (!StringUtils.hasText(message)) { - return; - } - - String type; - String data; - if (message.startsWith(TYPE_PUBLISH)) { - type = TYPE_PUBLISH; - data = message.replace(TYPE_PUBLISH, ""); - } else if (message.startsWith(TYPE_ROLLBACK)) { - type = TYPE_ROLLBACK; - data = message.replace(TYPE_ROLLBACK, ""); - } else if (message.startsWith(TYPE_TEST)) { - type = TYPE_TEST; - data = message.replace(TYPE_TEST, ""); - } else if (message.startsWith(TYPE_DELETE)) { - type = TYPE_DELETE; - data = message.replace(TYPE_DELETE, ""); - } else if (message.startsWith(TYPE_REFRESH)) { - type = TYPE_REFRESH; - // 需要刷新集合配置的节点IP列表 - data = message.replace(TYPE_REFRESH, ""); - } else { - LOGGER.warn(String.format("未知的聚合推送数据[%s]", message)); - return; - } - - switch (type) { - case TYPE_PUBLISH: - case TYPE_ROLLBACK: - case TYPE_TEST: - configLoader.addConfig(data); - break; - case TYPE_DELETE: - configLoader.deleteConfig(data); - break; - case TYPE_REFRESH: - this.refreshConfig(data); - break; - default: - break; - } - } - - private static final TypeReference> STRING_LIST_TYPE_REFERENCE = new TypeReference>() {}; - private void refreshConfig(String allowIps) throws Exception { - if (!this.checkIp(LOCAL_IP, allowIps)) { - // 本机IP不在刷新列表中,直接返回 - LOGGER.info("本机IP地址[{}]不在刷新IP列表[{}]中", LOCAL_IP, allowIps); - return; - } - - // 刷新配置 - configLoader.init(); - } - - private boolean checkIp(String clientIp, String allowIps) { - if (!StringUtils.hasText(allowIps)) { - return true; - } - - List allowIpList = JSON.parseObject(allowIps, STRING_LIST_TYPE_REFERENCE); - for (String allowIp : allowIpList) { - boolean allow = "*".equals(allowIp) || allowIp.equals(clientIp) - || allowIp.contains("-") && ipIsValid(allowIp, clientIp); - if (allow) { - return true; - } - } - return false; - } - - private static final String REGX_IP = "((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; - private static final String REGX_IPB = REGX_IP + "\\-" + REGX_IP; - - private static boolean ipIsValid(String allowIp, String clientIp) { - if (allowIp == null) { - return false; - } - - if (clientIp == null) { - return false; - } - allowIp = allowIp.trim(); - clientIp = clientIp.trim(); - if (!allowIp.matches(REGX_IPB) || !clientIp.matches(REGX_IP)) { - return false; - } - int idx = allowIp.indexOf('-'); - String[] sips = allowIp.substring(0, idx).split("\\."); - String[] sipe = allowIp.substring(idx + 1).split("\\."); - String[] sipt = clientIp.split("\\."); - long ips = 0L, ipe = 0L, ipt = 0L; - for (int i = 0; i < 4; ++i) { - ips = ips << 8 | Integer.parseInt(sips[i]); - ipe = ipe << 8 | Integer.parseInt(sipe[i]); - ipt = ipt << 8 | Integer.parseInt(sipt[i]); - } - if (ips > ipe) { - long t = ips; - ips = ipe; - ipe = t; - } - return ips <= ipt && ipt <= ipe; - } - - private static final String LOCAL_IP = AggregateChannelListener.getLocalIp(); - private static String getLocalIp() { - try { - InetAddress inetAddress = InetAddress.getLocalHost(); - return inetAddress.getHostAddress(); - } catch (Exception e) { - LOGGER.warn("获取本地IP地址异常", e); - return "unknown"; - } - } -} diff --git a/fizz-core/src/main/java/com/fizzgate/proxy/CallbackService.java b/fizz-core/src/main/java/com/fizzgate/proxy/CallbackService.java index dd14621..dbea2bb 100644 --- a/fizz-core/src/main/java/com/fizzgate/proxy/CallbackService.java +++ b/fizz-core/src/main/java/com/fizzgate/proxy/CallbackService.java @@ -17,6 +17,8 @@ package com.fizzgate.proxy; +import com.fizzgate.aggregate.web.loader.BaseAggregateResult; +import com.fizzgate.aggregate.web.service.AggregateService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.buffer.DataBuffer; @@ -31,8 +33,6 @@ import org.springframework.web.server.ServerWebExchange; import com.fizzgate.config.SystemConfig; import com.fizzgate.constants.CommonConstants; -import com.fizzgate.fizz.AggregateResult; -import com.fizzgate.fizz.AggregateService; import com.fizzgate.plugin.auth.ApiConfig; import com.fizzgate.plugin.auth.ApiConfigService; import com.fizzgate.plugin.auth.CallbackConfig; @@ -133,7 +133,7 @@ public class CallbackService { } else if (r instanceof ClientResponse) { return genServerResponse(exchange, (ClientResponse) r); } else { - return aggregateService.genAggregateResponse(exchange, (AggregateResult) r); + return aggregateService.genAggregateResponse(exchange, (BaseAggregateResult) r); } } ) @@ -147,7 +147,7 @@ public class CallbackService { }; } - private Function> arError(ServerWebExchange exchange, Receiver r, HttpMethod method, HttpHeaders headers, DataBuffer body) { + private Function> arError(ServerWebExchange exchange, Receiver r, HttpMethod method, HttpHeaders headers, DataBuffer body) { return t -> { log(exchange, r, method, headers, body, t); return Mono.just(new FailAggregateResult(t)); @@ -289,7 +289,7 @@ public class CallbackService { ; } - private Function> arError(CallbackReplayReq req, String service, String path) { + private Function> arError(CallbackReplayReq req, String service, String path) { return t -> { log(req, service, path, t); return Mono.just(new FailAggregateResult(t)); diff --git a/fizz-core/src/main/java/com/fizzgate/proxy/FailAggregateResult.java b/fizz-core/src/main/java/com/fizzgate/proxy/FailAggregateResult.java index d77f0b6..1dbdcff 100644 --- a/fizz-core/src/main/java/com/fizzgate/proxy/FailAggregateResult.java +++ b/fizz-core/src/main/java/com/fizzgate/proxy/FailAggregateResult.java @@ -17,13 +17,14 @@ package com.fizzgate.proxy; -import com.fizzgate.fizz.AggregateResult; + +import com.fizzgate.aggregate.web.loader.BaseAggregateResult; /** * @author hongqiaowei */ -public class FailAggregateResult extends AggregateResult { +public class FailAggregateResult extends BaseAggregateResult { public Throwable throwable; diff --git a/fizz-core/src/main/java/com/fizzgate/proxy/FizzWebClient.java b/fizz-core/src/main/java/com/fizzgate/proxy/FizzWebClient.java index a430b74..5e75b23 100644 --- a/fizz-core/src/main/java/com/fizzgate/proxy/FizzWebClient.java +++ b/fizz-core/src/main/java/com/fizzgate/proxy/FizzWebClient.java @@ -17,6 +17,7 @@ package com.fizzgate.proxy; +import com.fizzgate.aggregate.core.exception.FizzRuntimeException; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -26,7 +27,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; @@ -35,7 +35,6 @@ import org.springframework.web.reactive.function.client.WebClient; import com.fizzgate.config.ProxyWebClientConfig; import com.fizzgate.config.SystemConfig; import com.fizzgate.exception.ExternalService4xxException; -import com.fizzgate.fizz.exception.FizzRuntimeException; import com.fizzgate.service_registry.RegistryCenterService; import com.fizzgate.util.Consts; import com.fizzgate.util.NetworkUtils; diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/InputValidateTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/InputValidateTests.java deleted file mode 100644 index 40d923a..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/InputValidateTests.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.fizzgate.fizz; - -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.Pipeline; -import com.fizzgate.fizz.input.ClientInputConfig; -import com.fizzgate.fizz.input.Input; - -import com.fizzgate.schema.util.I18nUtils; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * aggregate input validate tests - * - * @author zhongjie - */ -class InputValidateTests { - private Map jsonSchemaDef = ONode.load("{\n" + - " \"properties\": {\n" + - " \"library\": {\n" + - " \"type\": \"string\"\n" + - " }\n" + - " },\n" + - " \"required\": [\n" + - " \"library\"\n" + - " ],\n" + - " \"type\": [\n" + - " \"object\",\n" + - " \"null\"\n" + - " ]\n" + - "}").toObject(Map.class); - - @Test - void inputValidateHeaderTipTest() { - Pipeline pipeline = new Pipeline(); - - ClientInputConfig clientInputConfig = new ClientInputConfig(); - clientInputConfig.setHeadersDef(jsonSchemaDef); - Input input = new Input(); - input.setConfig(clientInputConfig); - Map clientInput = new HashMap<>(16); - clientInput.put("headers", new HashMap<>(0)); - String validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.HEADER.tipZh)); - - I18nUtils.setContextLocale(new Locale("en")); - try { - validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.HEADER.tipEn)); - } finally { - I18nUtils.removeContextLocale(); - } - } - - @Test - void inputValidateQueryParamTipTest() { - Pipeline pipeline = new Pipeline(); - - ClientInputConfig clientInputConfig = new ClientInputConfig(); - clientInputConfig.setParamsDef(jsonSchemaDef); - Input input = new Input(); - input.setConfig(clientInputConfig); - Map clientInput = new HashMap<>(16); - clientInput.put("params", new HashMap<>(0)); - String validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.QUERY_PARAM.tipZh)); - - I18nUtils.setContextLocale(new Locale("en")); - try { - validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.QUERY_PARAM.tipEn)); - } finally { - I18nUtils.removeContextLocale(); - } - } - - @Test - void inputValidateBodyTipTest() { - Pipeline pipeline = new Pipeline(); - - ClientInputConfig clientInputConfig = new ClientInputConfig(); - clientInputConfig.setBodyDef(jsonSchemaDef); - Input input = new Input(); - input.setConfig(clientInputConfig); - Map clientInput = new HashMap<>(16); - clientInput.put("body", new HashMap<>(0)); - String validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.BODY.tipZh)); - - I18nUtils.setContextLocale(new Locale("en")); - try { - validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.BODY.tipEn)); - } finally { - I18nUtils.removeContextLocale(); - } - } - - @Test - void inputValidateScriptTipTest() { - Pipeline pipeline = new Pipeline(); - - ClientInputConfig clientInputConfig = new ClientInputConfig(); - Map scriptValidate = new HashMap<>(4); - scriptValidate.put("type", "groovy"); - scriptValidate.put("source", "import java.util.List; import java.util.ArrayList; " + - "List errorList = new ArrayList<>(1); errorList.add(\"same thing error\"); return errorList;"); - clientInputConfig.setScriptValidate(scriptValidate); - Input input = new Input(); - input.setConfig(clientInputConfig); - Map clientInput = new HashMap<>(16); - String validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.SCRIPT.tipZh)); - - I18nUtils.setContextLocale(new Locale("en")); - try { - validateMsg = pipeline.inputValidate(input, clientInput); - assertNotNull(validateMsg); - assertTrue(validateMsg.startsWith(Pipeline.ValidateType.SCRIPT.tipEn)); - } finally { - I18nUtils.removeContextLocale(); - } - } -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/component/CircleTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/component/CircleTests.java deleted file mode 100644 index 2e93bfd..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/component/CircleTests.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.component; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.component.OperatorEnum; -import com.fizzgate.fizz.component.StepContextPosition; -import com.fizzgate.fizz.component.circle.Circle; -import com.fizzgate.fizz.component.circle.CircleItem; -import com.fizzgate.fizz.component.condition.Condition; -import com.fizzgate.fizz.component.condition.ConditionValue; -import com.fizzgate.fizz.field.FixedDataTypeEnum; -import com.fizzgate.fizz.field.RefDataTypeEnum; -import com.fizzgate.fizz.field.ValueTypeEnum; -import com.fizzgate.fizz.input.PathMapping; - -class CircleTests { - @Test - void contextLoads() { - } - - @SuppressWarnings("rawtypes") - @Test - void testNextFixedDataSource() { - ONode ctxNode = ONode.load(new HashMap()); - - // FIXED data source - Circle c = new Circle(null, ValueTypeEnum.FIXED, 3, null, null); - CircleItem circleItem = c.next(ctxNode); - assertEquals(1, (Integer) circleItem.getItem()); - - circleItem = c.next(ctxNode); - assertEquals(2, (Integer) circleItem.getItem()); - - circleItem = c.next(ctxNode); - assertEquals(3, (Integer) circleItem.getItem()); - - circleItem = c.next(ctxNode); - assertEquals(null, circleItem); - - } - - @Test - void testNextRefDataSource() { - ONode ctxNode = ONode.load(new HashMap()); - - List list1 = new ArrayList<>(); - list1.add("1"); - list1.add("2"); - list1.add("3"); - PathMapping.setByPath(ctxNode, "data.list1", list1, true); - - // REF data source - Circle c = new Circle(null, ValueTypeEnum.REF, "data.list1", null, null); - CircleItem circleItem = c.next(ctxNode); - assertEquals("1", (String) circleItem.getItem()); - - circleItem = c.next(ctxNode); - assertEquals("2", (String) circleItem.getItem()); - - circleItem = c.next(ctxNode); - assertEquals("3", (String) circleItem.getItem()); - - circleItem = c.next(ctxNode); - assertEquals(null, circleItem); - - } - - @Test - void testExecCondition() { - ONode ctxNode = ONode.load(new HashMap()); - - List list1 = new ArrayList<>(); - list1.add("0"); - list1.add("1"); - list1.add("2"); - list1.add("3"); - list1.add("4"); - - PathMapping.setByPath(ctxNode, "data.list1", list1, true); - - ConditionValue bValue1 = new ConditionValue(ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "3"); - ConditionValue bValue2 = new ConditionValue(ValueTypeEnum.REF, RefDataTypeEnum.STRING, "item"); - Condition c1 = new Condition(null, bValue1, OperatorEnum.NE, bValue2); - - List execConditions = new ArrayList<>(); - execConditions.add(c1); - - Circle circle = new Circle(null, ValueTypeEnum.REF, "data.list1", execConditions, null); - - for (int i = 0; i < 5; i++) { - CircleItem circleItem = circle.next(ctxNode); - PathMapping.setByPath(ctxNode, "item", circleItem.getItem(), true); - PathMapping.setByPath(ctxNode, "index", circleItem.getIndex(), true); - boolean rs = circle.canExec(circleItem.getIndex(), ctxNode, new StepContext(), - new StepContextPosition("step1", null)); - assertEquals(i, circleItem.getIndex()); - if (i < 3) { - assertEquals(true, rs); - } - if (i == 3) { - assertEquals(false, rs); - break; - } - } - - } - - @Test - void testBreakCondition() { - ONode ctxNode = ONode.load(new HashMap()); - - List list1 = new ArrayList<>(); - list1.add("0"); - list1.add("1"); - list1.add("2"); - list1.add("3"); - list1.add("4"); - - PathMapping.setByPath(ctxNode, "data.list1", list1, true); - - ConditionValue bValue1 = new ConditionValue(ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "3"); - ConditionValue bValue2 = new ConditionValue(ValueTypeEnum.REF, RefDataTypeEnum.STRING, "item"); - Condition c1 = new Condition(null, bValue1, OperatorEnum.EQ, bValue2); - - List breakConditions = new ArrayList<>(); - breakConditions.add(c1); - - Circle circle = new Circle(null, ValueTypeEnum.REF, "data.list1", null, breakConditions); - - for (int i = 0; i < 5; i++) { - CircleItem circleItem = circle.next(ctxNode); - PathMapping.setByPath(ctxNode, "item", circleItem.getItem(), true); - PathMapping.setByPath(ctxNode, "index", circleItem.getIndex(), true); - - boolean rs = circle.breakCircle(circleItem.getIndex(), ctxNode, new StepContext(), - new StepContextPosition("step1", null)); - assertEquals(i, circleItem.getIndex()); - if (i < 3) { - assertEquals(false, rs); - } - if (i == 3) { - assertEquals(true, rs); - break; - } - } - - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/component/ConditionTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/component/ConditionTests.java deleted file mode 100644 index b28df21..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/component/ConditionTests.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.component; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import javax.validation.constraints.AssertTrue; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.component.OperatorEnum; -import com.fizzgate.fizz.component.condition.Condition; -import com.fizzgate.fizz.component.condition.ConditionValue; -import com.fizzgate.fizz.field.FixedDataTypeEnum; -import com.fizzgate.fizz.field.RefDataTypeEnum; -import com.fizzgate.fizz.field.ValueTypeEnum; -import com.fizzgate.fizz.input.PathMapping; - -/** - * - * @author Francis Dong - * - */ -class ConditionTests { - @Test - void contextLoads() { - } - - private static final Boolean TRUE = true; - private static final Boolean FALSE = false; - - @Test - void testExec() { - ONode ctxNode = ONode.load(new HashMap()); - - Map m = new HashMap<>(); - m.put("int", 1); - m.put("long", 2); - m.put("float", 3.1); - m.put("double", 4.21); - m.put("string_abcd", "abcd"); - m.put("string_1", "1"); - m.put("string_8", "8"); - m.put("string_blank", ""); - m.put("bool_true", true); - m.put("bool_false", false); - - List list1 = new ArrayList<>(); - list1.add("0"); - list1.add("1"); - list1.add("2"); - list1.add("3"); - list1.add("4"); - - List list2 = new ArrayList<>(); - list2.add("1"); - list2.add("3"); - list2.add("223"); - - List intList = new ArrayList<>(); - intList.add(0); - intList.add(1); - intList.add(2); - intList.add(3); - intList.add(4); - - List floatList = new ArrayList<>(); - floatList.add(0f); - floatList.add(1f); - floatList.add(2f); - floatList.add(3f); - floatList.add(4f); - - PathMapping.setByPath(ctxNode, "data.m", m, true); - PathMapping.setByPath(ctxNode, "data.list1", list1, true); - PathMapping.setByPath(ctxNode, "data.list2", list2, true); - PathMapping.setByPath(ctxNode, "data.intList", intList, true); - PathMapping.setByPath(ctxNode, "data.floatList", floatList, true); - PathMapping.setByPath(ctxNode, "data.emptyList", new ArrayList<>(), true); - - // boolean - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, TRUE, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.EQ, FALSE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.REF, - RefDataTypeEnum.BOOLEAN, "data.m.bool_true", OperatorEnum.EQ, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.REF, - RefDataTypeEnum.BOOLEAN, "data.m.bool_false", OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.REF, - RefDataTypeEnum.BOOLEAN, "data.m.bool_true", OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.REF, null, - "data.m.a", OperatorEnum.EQ, FALSE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.GT, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.GE, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, TRUE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, TRUE, OperatorEnum.GE, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, TRUE, OperatorEnum.LT, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.BOOLEAN, FALSE, ValueTypeEnum.FIXED, - FixedDataTypeEnum.BOOLEAN, FALSE, OperatorEnum.LE, TRUE }); - - // number - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 1, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 2, OperatorEnum.EQ, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 1.000, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 2.1, OperatorEnum.EQ, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.0, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 1.000, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 2.1, OperatorEnum.EQ, FALSE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 2.1, OperatorEnum.GT, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 0.1, OperatorEnum.GE, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 0.1, OperatorEnum.LT, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 1.1, ValueTypeEnum.FIXED, - FixedDataTypeEnum.NUMBER, 4, OperatorEnum.LT, TRUE }); - - // collection - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "2", OperatorEnum.CONTAINS, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_1", OperatorEnum.CONTAINS, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.CONTAINS, FALSE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "2", OperatorEnum.NOTCONTAIN, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_1", OperatorEnum.NOTCONTAIN, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.NOTCONTAIN, TRUE }); - - // collection contains any - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.ARRAY, "data.list2", OperatorEnum.CONTAINSANY, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.ARRAY, "data.intList", OperatorEnum.CONTAINSANY, FALSE }); - - // Collection - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "2", OperatorEnum.CONTAINS, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.REF, - RefDataTypeEnum.INT, "data.m.int", OperatorEnum.CONTAINS, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED, - RefDataTypeEnum.INT, 2, OperatorEnum.CONTAINS, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED, - RefDataTypeEnum.INT, 9, OperatorEnum.CONTAINS, FALSE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "2", OperatorEnum.NOTCONTAIN, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.REF, - RefDataTypeEnum.INT, "data.m.int", OperatorEnum.NOTCONTAIN, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED, - RefDataTypeEnum.INT, 2, OperatorEnum.NOTCONTAIN, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.intList", ValueTypeEnum.FIXED, - RefDataTypeEnum.INT, 9, OperatorEnum.NOTCONTAIN, TRUE }); - - // Collection - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList", - ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "2", OperatorEnum.CONTAINS, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList", ValueTypeEnum.REF, - RefDataTypeEnum.INT, "data.m.int", OperatorEnum.CONTAINS, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList", - ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 2, OperatorEnum.CONTAINS, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.floatList", - ValueTypeEnum.FIXED, FixedDataTypeEnum.NUMBER, 2.0, OperatorEnum.CONTAINS, TRUE }); - - // String - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, null, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, null, OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "1", OperatorEnum.EQ, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "1", OperatorEnum.EQ, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.EQ, FALSE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, null, OperatorEnum.NE, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, null, OperatorEnum.NE, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, null, ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "1", OperatorEnum.NE, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "1", OperatorEnum.NE, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.NE, TRUE }); - - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "1", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.GT, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.GT, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.GE, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.LT, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.FIXED, FixedDataTypeEnum.STRING, "11", ValueTypeEnum.FIXED, - FixedDataTypeEnum.STRING, "21", OperatorEnum.LE, TRUE }); - - // Is null - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNULL, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", - ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNULL, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null, - OperatorEnum.ISNULL, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null, - OperatorEnum.ISNULL, FALSE }); - - // Is not null - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNOTNULL, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", - ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNOTNULL, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null, - OperatorEnum.ISNOTNULL, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null, - OperatorEnum.ISNOTNULL, TRUE }); - - // Is Blank - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISBLANK, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", - ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISBLANK, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null, - OperatorEnum.ISBLANK, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null, - OperatorEnum.ISBLANK, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_blank", null, null, - null, OperatorEnum.ISBLANK, TRUE }); - - // Is not Blank - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNOTBLANK, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", - ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNOTBLANK, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null, - OperatorEnum.ISNOTBLANK, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_8", null, null, null, - OperatorEnum.ISNOTBLANK, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.STRING, "data.m.string_blank", null, null, - null, OperatorEnum.ISNOTBLANK, FALSE }); - - // Is empty - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISEMPTY, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISEMPTY, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null, - OperatorEnum.ISEMPTY, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", null, null, null, - OperatorEnum.ISEMPTY, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.emptyList", null, null, null, - OperatorEnum.ISEMPTY, TRUE }); - - // Is not empty - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNOTEMPTY, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", ValueTypeEnum.REF, - RefDataTypeEnum.STRING, "data.m.string_8", OperatorEnum.ISNOTEMPTY, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list11111", null, null, null, - OperatorEnum.ISNOTEMPTY, FALSE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.list1", null, null, null, - OperatorEnum.ISNOTEMPTY, TRUE }); - this.run(ctxNode, new Object[] { ValueTypeEnum.REF, RefDataTypeEnum.ARRAY, "data.emptyList", null, null, null, - OperatorEnum.ISNOTEMPTY, FALSE }); - - } - - private void run(ONode ctxNode, Object[] item) { - ConditionValue bValue1 = null; - if (item[1] instanceof FixedDataTypeEnum) { - bValue1 = new ConditionValue((ValueTypeEnum) item[0], (FixedDataTypeEnum) item[1], item[2]); - } else { - bValue1 = new ConditionValue((ValueTypeEnum) item[0], (RefDataTypeEnum) item[1], item[2]); - } - ConditionValue bValue2 = null; - if (item[3] != null) { - if (item[4] instanceof FixedDataTypeEnum) { - bValue2 = new ConditionValue((ValueTypeEnum) item[3], (FixedDataTypeEnum) item[4], item[5]); - } else { - bValue2 = new ConditionValue((ValueTypeEnum) item[3], (RefDataTypeEnum) item[4], item[5]); - } - } - Condition c = new Condition(null, bValue1, (OperatorEnum) item[6], bValue2); - boolean rs = c.exec(ctxNode); - boolean expected = (boolean) item[7]; - assertEquals(expected, rs); - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/CodecFuncTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/CodecFuncTests.java deleted file mode 100644 index 0c1609b..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/CodecFuncTests.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.function.FuncExecutor; -import com.fizzgate.fizz.input.PathMapping; -import com.fizzgate.util.DigestUtils; - -/** - * - * @author Francis Dong - * - */ -class CodecFuncTests { - @Test - void contextLoads() { - } - - - @Test - void testMd5() { - String funcExpression = "fn.codec.md5(\"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("900150983cd24fb0d6963f7d28e17f72", result.toString()); - } - - @Test - void testMd5_2() { - String funcExpression = "fn.codec.md5(fn.date.add(fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, fn.math.addExact(999,1)), \"yyyy-MM-dd HH:mm:ss\", fn.math.addExact(0,1), 1000))"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(DigestUtils.md5Hex("2021-07-09 22:44:57"), result.toString()); - } - - @Test - void testSha1() { - String funcExpression = "fn.codec.sha1(\"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", result.toString()); - } - - @Test - void testSha256() { - String funcExpression = "fn.codec.sha256(\"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", result.toString()); - } - - @Test - void testSha384() { - String funcExpression = "fn.codec.sha384(\"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", result.toString()); - } - - @Test - void testSha512() { - String funcExpression = "fn.codec.sha512(\"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", result.toString()); - } - - @Test - void testBase64Encode() { - String funcExpression = "fn.codec.base64Encode(\"Base64编码介绍\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("QmFzZTY057yW56CB5LuL57uN", result.toString()); - } - - // @Test - void testBase64Decode() { - String funcExpression = "fn.codec.base64Decode(\"QmFzZTY057yW56CB5LuL57uN\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("Base64编码介绍", result.toString()); - } - - @Test - void testAesEncrypt() { - String funcExpression = "fn.codec.aesEncrypt(\"abc\", \"1234567812345678\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("ucSL5R/jQigQ1dxzsWi2kg==", result.toString()); - } - - @Test - void testAesDecrypt() { - String funcExpression = "fn.codec.aesDecrypt(\"ucSL5R/jQigQ1dxzsWi2kg==\", \"1234567812345678\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("abc", result.toString()); - } - - @Test - void testDesEncrypt() { - String funcExpression = "fn.codec.desEncrypt(\"abc\", \"12345678123456781234567812345678\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("9YR6ZPdZufM=", result.toString()); - } - - @Test - void testDesDecrypt() { - String funcExpression = "fn.codec.desDecrypt(\"9YR6ZPdZufM=\", \"12345678123456781234567812345678\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("abc", result.toString()); - } - - @Test - void testHmacSha256() { - String funcExpression = "fn.codec.hmacSha256(\"12345678123456781234567812345678\", \"635e8562b968bc05bb80cacf124ebd53285280ee6845df0000faa33acafc38f0\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("c61be0237ec186df1c5f51425e607093b260a76e5de43a62cb3e821103303990", result.toString()); - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/CommonFuncTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/CommonFuncTests.java deleted file mode 100644 index a195e6a..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/CommonFuncTests.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.function.FuncExecutor; -import com.fizzgate.fizz.input.PathMapping; - -/** - * - * @author Francis Dong - * - */ -class CommonFuncTests { - - @Test - void contextLoads() { - } - - private Map createRecord(String key, Object value) { - Map m = new HashMap<>(); - m.put(key, value); - return m; - } - - private ONode getCtxNode() { - ONode ctxNode = ONode.load(new HashMap()); - - Map m = new HashMap<>(); - m.put("a", "1"); - m.put("b", "1"); - m.put("d", ""); - m.put("true", true); - m.put("false", false); - m.put("blank", ""); - m.put("null", null); - m.put("stringtrue", "true"); - m.put("stringfalse", "false"); - m.put("string1", "1"); - m.put("string0", "0"); - m.put("stringabc", "abc"); - m.put("int1", "1"); - m.put("int0", "0"); - m.put("int2", "2"); - - List list = new ArrayList<>(); - list.add(createRecord("a", "a1")); - list.add(createRecord("a", "a2")); - list.add(createRecord("a", "a3")); - - List list2 = new ArrayList<>(); - - PathMapping.setByPath(ctxNode, "data.m", m, true); - PathMapping.setByPath(ctxNode, "data.m2", new HashMap<>(), true); - PathMapping.setByPath(ctxNode, "data.list", list, true); - PathMapping.setByPath(ctxNode, "data.list2", list2, true); - return ctxNode; - } - - @Test - void testIif() { - String funcExpression = "fn.common.iif(true, \"abc\", \"xyz\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("abc", result.toString()); - } - - - @Test - void testIif2() { - String funcExpression = "fn.common.iif(false, \"abc\", \"xyz\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("xyz", result.toString()); - } - - @Test - void testIif3() { - String funcExpression = "fn.common.iif(false, \"abc\", 123)"; - Long result = (Long)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(123, result.intValue()); - } - - @Test - void testIif4() { - String funcExpression = "fn.common.iif(false, \"abc\", 123.4)"; - Double result = (Double)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(123.4, result); - } - - @Test - void testIif5() { - String funcExpression = "fn.common.iif(false, \"abc\", true)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals() { - String funcExpression = "fn.common.equals(\"abc\", true)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(false, result); - } - - @Test - void testEquals2() { - String funcExpression = "fn.common.equals(\"abc\", \"abc\")"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals3() { - String funcExpression = "fn.common.equals(123, 123)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals4() { - String funcExpression = "fn.common.equals(123, 123.0)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(false, result); - } - - @Test - void testEquals5() { - String funcExpression = "fn.common.equals(true, true)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals6() { - String funcExpression = "fn.common.equals(123.2, 123.2)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals7() { - String funcExpression = "fn.common.equals(null, 123.2)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(false, result); - } - - @Test - void testEquals8() { - String funcExpression = "fn.common.equals(null, null)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals9() { - String funcExpression = "fn.common.equals(123, null)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(false, result); - } - - @Test - void testIsNull() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.isNull({data.m.c})"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - } - - @Test - void testIsNotNull() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.isNotNull({data.m.a})"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - } - - @Test - void testIsBlank() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.isBlank({data.m.c})"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - } - - @Test - void testIsNotBlank() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.isNotBlank({data.m.a})"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - } - - @Test - void testIsEmpty() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.isEmpty({data.list2})"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.isEmpty({data.m2})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.isEmpty({data.m.x})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.isEmpty({data.m.d})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - } - - @Test - void testIsNotEmpty() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.isNotEmpty({data.list})"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.isNotEmpty({data.m})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.isNotEmpty({data.m.a})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.isNotEmpty({data.m.y})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - } - - @Test - void testAnd() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.and(true, true)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and(true, true, true)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and(true)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and({data.m.true}, {data.m.true}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and({data.m.true}, {data.m.true}, {data.m.true}, {data.m.notexist})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.true}, {data.m.true}, {data.m.true}, {data.m.null})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.true}, {data.m.true}, {data.m.true}, {data.m.blank})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.true}, {data.m.false}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.false}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.false})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.true}, {data.m.notexist})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.notexist})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and()"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.blank})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.null})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.int1})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and({data.m.int0})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.string1})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and({data.m.string0})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.stringabc})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.stringtrue})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.and({data.m.stringfalse})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.and({data.m.int2})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - } - - @Test - void testOr() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.or(true, true)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or(true, true, true)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or(false, true)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or(true)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or(false)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or(true, false, true)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.true}, {data.m.true}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.true}, {data.m.true}, {data.m.false})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.false}, {data.m.true}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.false}, {data.m.notexist}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.false}, {data.m.null}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.false}, {data.m.blank}, {data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.false}, {data.m.false}, {data.m.false})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.false}, {data.m.false})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.false})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.null})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.blank})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.notexist})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or()"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.true})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.int1})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.int0})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.string1})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.string0})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.stringabc})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.stringtrue})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.or({data.m.stringfalse})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.or({data.m.int2})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - } - - @Test - void testNot() { - ONode ctx = getCtxNode(); - String funcExpression = "fn.common.not(true)"; - Boolean result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.not(false)"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.blank})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.null})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.notexist})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.string1})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.not({data.m.string0})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.stringabc})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.stringtrue})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.not({data.m.stringfalse})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.int1})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(false, result); - - funcExpression = "fn.common.not({data.m.int0})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - - funcExpression = "fn.common.not({data.m.int2})"; - result = (Boolean)FuncExecutor.getInstance().exec(ctx, funcExpression); - assertEquals(true, result); - } -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/DateFuncTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/DateFuncTests.java deleted file mode 100644 index 075b75b..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/DateFuncTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import com.fizzgate.fizz.function.FuncExecutor; - -/** - * - * @author Francis Dong - * - */ -class DateFuncTests { - @Test - void contextLoads() { - } - - @Test - void testTimestamp() { - String funcExpression = "fn.date.timestamp()"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - System.out.println(result); - } - - @Test - void testNow() { - String funcExpression = "fn.date.now(null)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - System.out.println(result); - } - -// @Test -// void testNow2() { -// String funcExpression = "fn.date.now()"; -// Object result = FuncExecutor.getInstance().exec(null, funcExpression); -// System.out.println(result); -// } - - @Test - void testGetTime() { - String funcExpression = "fn.date.getTime(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\")"; - Long result = (Long)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(1625841895000l, result.longValue()); - } - - @Test - void testAdd() { - String funcExpression = "fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, 1000)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:56", result.toString()); - } - - @Test - void testEmbeddedAdd() { - String funcExpression = "fn.date.add(fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, 1000), \"yyyy-MM-dd HH:mm:ss\", 1, 1000)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:57", result.toString()); - } - - - @Test - void testFormatTs() { - String funcExpression = "fn.date.formatTs(1628825352227, \"yyyy-MM-dd HH:mm:ss\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-08-13 11:29:12", result.toString()); - } - - @Test - void testChangePattern() { - String funcExpression = "fn.date.changePattern(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", \"MM-dd HH:mm\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("07-09 22:44", result.toString()); - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/FuncExecutorTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/FuncExecutorTests.java deleted file mode 100644 index 2972aed..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/FuncExecutorTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import com.fizzgate.fizz.function.CodecFunc; -import com.fizzgate.fizz.function.FuncExecutor; - -/** - * - * @author Francis Dong - * - */ -class FuncExecutorTests { - - @Test - void contextLoads() { - } - - - @Test - void testNest() { - String funcExpression = "fn.codec.md5(fn.date.add( fn.date.add(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\", 1, fn.math.addExact(999,1 ) ), \"yyyy-MM-dd HH:mm:ss\", fn.math.addExact(0,1), 1000 ))"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(CodecFunc.getInstance().md5("2021-07-09 22:44:57"), result); - } - - @Test - void testNest2() { - String funcExpression = "fn.string.toUpperCase(fn.codec.sha256(fn.string.concat(\"a\",\"b\",fn.string.toString(fn.date.timestamp()))))"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - System.out.println(result); - } - - @Test - void testNest3() { - String funcExpression = "fn.string.toUpperCase(fn.string.concat(\"a\",\"b\", fn.string.concat())))"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("AB", result); - } - - @Test - void testNest4() { - String funcExpression = "fn.string.toUpperCase(fn.codec.sha256(fn.string.concat(\"a\",fn.string.concat(),\"b\")))"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - System.out.println(result); - } - - @Test - void testNest5() { - String funcExpression = "fn.string.toUpperCase(fn.codec.sha256(fn.string.concat(\"a\",fn.string.toString(fn.date.timestamp()),fn.string.concat(fn.string.concat(fn.string.concat(fn.string.concat())), \"c\"),\"b\")))"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - System.out.println(result); - } - - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/ListFuncTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/ListFuncTests.java deleted file mode 100644 index 81957fa..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/ListFuncTests.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.alibaba.fastjson.JSON; -import com.fizzgate.fizz.function.FuncExecutor; -import com.fizzgate.fizz.input.PathMapping; - -/** - * - * @author Francis Dong - * - */ -@SuppressWarnings({ "rawtypes", "unchecked" }) -class ListFuncTests { - @Test - void contextLoads() { - } - - private Map createRecord(String key, Object value) { - Map m = new HashMap<>(); - m.put(key, value); - return m; - } - - private Map createRecord2(int index) { - Map m = new HashMap<>(); - m.put("a", "a" + index); - m.put("b", "b" + index); - m.put("c", "c" + index); - m.put("d", "d" + index); - m.put("e", "e" + index); - return m; - } - - @Test - void testExpand() { - List> data = new ArrayList<>(); - - List subList1 = new ArrayList<>(); - subList1.add(createRecord("a", "a1")); - subList1.add(createRecord("a", "a2")); - subList1.add(createRecord("a", "a3")); - - List subList2 = new ArrayList<>(); - subList2.add(createRecord("a", "a4")); - subList2.add(createRecord("a", "a5")); - subList2.add(createRecord("a", "a6")); - - data.add(subList1); - data.add(subList2); - - ONode ctxNode = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode, "test.data", data, true); - - String funcExpression = "fn.list.expand({test.data})"; - List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); - assertEquals(6, result.size()); - assertEquals("a2", ((Map) result.get(1)).get("a").toString()); - assertEquals("a4", ((Map) result.get(3)).get("a").toString()); - } - - @Test - void testMerge() { - List subList1 = new ArrayList<>(); - subList1.add(createRecord("a", "a1")); - subList1.add(createRecord("a", "a2")); - subList1.add(createRecord("a", "a3")); - - List subList2 = new ArrayList<>(); - subList2.add(createRecord("a", "a4")); - subList2.add(createRecord("a", "a5")); - subList2.add(createRecord("a", "a6")); - - ONode ctxNode = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode, "test.data1", subList1, true); - PathMapping.setByPath(ctxNode, "test.data2", subList2, true); - - String funcExpression = "fn.list.merge({test.data1}, {test.data2})"; - List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); - assertEquals(6, result.size()); - assertEquals("a2", ((Map) result.get(1)).get("a").toString()); - assertEquals("a4", ((Map) result.get(3)).get("a").toString()); - } - - @Test - void testExtract() { - List subList1 = new ArrayList<>(); - subList1.add(createRecord2(1)); - subList1.add(createRecord2(2)); - subList1.add(createRecord2(3)); - subList1.add(createRecord2(4)); - subList1.add(createRecord2(5)); - - ONode ctxNode = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode, "test.data", subList1, true); - - String funcExpression = "fn.list.extract({test.data}, \"c\",\"b\", \"e\")"; - List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); - assertEquals(5, result.size()); - assertEquals("c2", ((Map) result.get(1)).get("c").toString()); - assertEquals("e4", ((Map) result.get(3)).get("e").toString()); - assertEquals(null, ((Map) result.get(3)).get("a")); - assertEquals(null, ((Map) result.get(3)).get("d")); -// System.out.println(result); - } - - private Map createRecord3(int index, boolean isDest) { - Map m = new HashMap<>(); - m.put("a", "a" + index); - String s = isDest ? "" + index : index + "-abc"; - m.put("b", "b" + s); - m.put("c", "c" + s); - if (!isDest) { - m.put("d", "d" + s); - m.put("e", "e" + s); - } - return m; - } - - @Test - void testJoin() { - List list1 = new ArrayList<>(); - list1.add(createRecord3(1, true)); - list1.add(createRecord3(2, true)); - list1.add(createRecord3(3, true)); - list1.add(createRecord3(4, true)); - list1.add(createRecord3(5, true)); - - List list2 = new ArrayList<>(); - list2.add(createRecord3(1, false)); - list2.add(createRecord3(2, false)); - list2.add(createRecord3(3, false)); - list2.add(createRecord3(4, false)); - - ONode ctxNode = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode, "test.list1", list1, true); - PathMapping.setByPath(ctxNode, "test.list2", list2, true); - - String funcExpression = "fn.list.join({test.list1}, {test.list2},\"a\",\"c\",\"d\")"; - List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); - assertEquals(5, result.size()); - assertEquals("a2", ((Map) result.get(1)).get("a").toString()); - assertEquals("d4-abc", ((Map) result.get(3)).get("d").toString()); - // System.out.println(JSON.toJSONString(result)); - - String json1 = "[\n" - + " {\n" - + " \"itemType\": \"3\",\n" - + " \"currCd\": \"156\",\n" - + " \"batchDate\": \"20190331\",\n" - + " \"itemCd\": \"ORGAP0008\",\n" - + " \"itemVal\": 0.7189,\n" - + " \"itemNm\": \"balance rate\",\n" - + " \"valueStr\": \"0.7189\",\n" - + " \"orgCd1\": \"230009999\"\n" - + " },\n" - + " {\n" - + " \"itemType\": \"3\",\n" - + " \"currCd\": \"156\",\n" - + " \"batchDate\": \"20190331\",\n" - + " \"itemCd\": \"ORGAP0008\",\n" - + " \"itemVal\": 0.7040,\n" - + " \"itemNm\": \"balance rate\",\n" - + " \"valueStr\": \"0.7040\",\n" - + " \"orgCd1\": \"230009999\"\n" - + " },\n" - + " {\n" - + " \"itemType\": \"3\",\n" - + " \"currCd\": \"156\",\n" - + " \"batchDate\": \"20190331\",\n" - + " \"itemCd\": \"ORGAP0008\",\n" - + " \"itemVal\": 0.7040,\n" - + " \"itemNm\": \"balance rate\",\n" - + " \"valueStr\": \"0.7040\",\n" - + " \"orgCd1\": \"231009999\"\n" - + " }\n" - + " ]"; - - String json2 = "[\n" - + " {\n" - + " \"orgName\": \"Name1\",\n" - + " \"orgCd\": \"230009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name2\",\n" - + " \"orgCd\": \"231009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name3\",\n" - + " \"orgCd\": \"232009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name3\",\n" - + " \"orgCd\": \"233009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name4\",\n" - + " \"orgCd\": \"234009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name5\",\n" - + " \"orgCd\": \"235009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name6\",\n" - + " \"orgCd\": \"236009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name7\",\n" - + " \"orgCd\": \"237009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name8\",\n" - + " \"orgCd\": \"238009999\"\n" - + " },\n" - + " {\n" - + " \"orgName\": \"Name9\",\n" - + " \"orgCd\": \"239009999\"\n" - + " }\n" - + " ]"; - - - ONode ctxNode2 = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode2, "test.list1", JSON.parseArray(json1, Map.class), true); - PathMapping.setByPath(ctxNode2, "test.list2", JSON.parseArray(json2, Map.class), true); - String funcExpression2 = "fn.list.join({test.list1},{test.list2},\"orgCd1:orgCd\")"; - List result2 = (List) FuncExecutor.getInstance().exec(ctxNode2, funcExpression2); - System.out.println(JSON.toJSONString(result2)); - assertEquals("Name1", ((Map) result2.get(0)).get("orgName").toString()); - } - - @Test - void testRename() { - List subList1 = new ArrayList<>(); - subList1.add(createRecord2(1)); - subList1.add(createRecord2(2)); - subList1.add(createRecord2(3)); - subList1.add(createRecord2(4)); - subList1.add(createRecord2(5)); - - List subList2 = new ArrayList<>(); - subList2.add(createRecord2(1)); - Map m2 = createRecord2(2); - m2.put("b", "b22222"); - subList2.add(m2); - subList2.add(createRecord2(3)); - Map m4 = createRecord2(4); - m4.put("e", "e44444"); - subList2.add(m4); - subList2.add(createRecord2(5)); - - ONode ctxNode = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode, "test.data", subList1, true); - PathMapping.setByPath(ctxNode, "test.data2", subList2, true); - - String funcExpression = "fn.list.rename({test.data}, \"b:birthday\", \"e:email\")"; - List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); - System.out.println(JSON.toJSONString(result)); - assertEquals(5, result.size()); - assertEquals("b2", ((Map) result.get(1)).get("birthday").toString()); - assertEquals("e4", ((Map) result.get(3)).get("email").toString()); - assertEquals(null, ((Map) result.get(3)).get("b")); - assertEquals(null, ((Map) result.get(3)).get("e")); - - - String funcExpression2 = "fn.list.join({test.data}, fn.list.rename({test.data2}, \"b:birthday\", \"e:email\"), \"a\", \"birthday\", \"email\")"; - List result2 = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression2); - System.out.println(JSON.toJSONString(result2)); - assertEquals(5, result2.size()); - assertEquals("b2", ((Map) result2.get(1)).get("b").toString()); - assertEquals("e4", ((Map) result2.get(3)).get("e").toString()); - assertEquals("b22222", ((Map) result2.get(1)).get("birthday").toString()); - assertEquals("e44444", ((Map) result2.get(3)).get("email").toString()); - } - - @Test - void testRemoveFields() { - List subList1 = new ArrayList<>(); - subList1.add(createRecord2(1)); - subList1.add(createRecord2(2)); - subList1.add(createRecord2(3)); - subList1.add(createRecord2(4)); - subList1.add(createRecord2(5)); - - ONode ctxNode = ONode.load(new HashMap()); - PathMapping.setByPath(ctxNode, "test.data", subList1, true); - - String funcExpression = "fn.list.removeFields({test.data}, \"b\", \"e\")"; - List result = (List) FuncExecutor.getInstance().exec(ctxNode, funcExpression); - System.out.println(JSON.toJSONString(result)); - assertEquals(5, result.size()); - assertEquals(null, ((Map) result.get(1)).get("b")); - assertEquals(null, ((Map) result.get(1)).get("e")); - assertEquals(null, ((Map) result.get(3)).get("b")); - assertEquals(null, ((Map) result.get(3)).get("e")); - - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/MathFuncTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/MathFuncTests.java deleted file mode 100644 index 8f2f07d..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/MathFuncTests.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import com.fizzgate.fizz.function.FuncExecutor; - -/** - * - * @author Francis Dong - * - */ -class MathFuncTests { - @Test - void contextLoads() { - } - - @Test - void testAbsExact() { - String funcExpression = "fn.math.absExact(-3)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(3, result); - } - - @Test - void testNegateExact() { - String funcExpression = "fn.math.negateExact(4)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(-4, result); - } - - @Test - void testNegateExact2() { - String funcExpression = "fn.math.negateExact(-4)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(4, result); - } - - @Test - void testAddExact() { - String funcExpression = "fn.math.addExact(14,-1)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(13, result); - } - - @Test - void testSubtractExact() { - String funcExpression = "fn.math.subtractExact(14,-1)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(15, result); - } - - @Test - void testMultiplyExact() { - String funcExpression = "fn.math.multiplyExact(14,2)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(28, result); - } - - @Test - void testMaxExact() { - String funcExpression = "fn.math.maxExact(14,2)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(14, result); - } - - @Test - void testMinExact() { - String funcExpression = "fn.math.minExact(14,2)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(2, result); - } - - @Test - void testMod() { - String funcExpression = "fn.math.mod(13,2)"; - long result = (long) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(1, result); - } - - @Test - void testPow() { - String funcExpression = "fn.math.pow(2,3)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(8, result); - } - - @Test - void testSqrt() { - String funcExpression = "fn.math.sqrt(4)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(2, result); - } - - @Test - void testAbsDecimal() { - String funcExpression = "fn.math.absDecimal(-4)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(4, result); - } - - @Test - void testNegateDecimal() { - String funcExpression = "fn.math.negateDecimal(4)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(-4, result); - } - - @Test - void testSubtractDecimal() { - String funcExpression = "fn.math.subtractDecimal(4,1.3)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(2.7, result); - } - - @Test - void testMultiplyDecimal() { - String funcExpression = "fn.math.multiplyDecimal(4,2.2)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(8.8, result); - } - - @Test - void testDivideDecimal() { - String funcExpression = "fn.math.divideDecimal(4.8,2)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(2.4, result); - } - - @Test - void testMaxDecimal() { - String funcExpression = "fn.math.maxDecimal(4.8,2)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(4.8, result); - } - - @Test - void testMinDecimal() { - String funcExpression = "fn.math.minDecimal(4.8,2)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(2, result); - } - - @Test - void testScaleDecimal() { - String funcExpression = "fn.math.scaleDecimal(4.8456,2)"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(4.85, result); - } - - @Test - void testRandom() { - String funcExpression = "fn.math.random()"; - double result = (double) FuncExecutor.getInstance().exec(null, funcExpression); - // System.out.println(result); - } - - @Test - void testcompare() { - String funcExpression = "fn.math.compare(4.8456,2)"; - int result = (int) FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(1, result); - } - - @Test - void testequals() { - String funcExpression = "fn.math.equals(3,3)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testLt() { - String funcExpression = "fn.math.lt(3,23)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testle() { - String funcExpression = "fn.math.le(3,3)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testgt() { - String funcExpression = "fn.math.gt(3,1)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testge() { - String funcExpression = "fn.math.ge(3,3)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/function/StringFuncTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/function/StringFuncTests.java deleted file mode 100644 index bf03b75..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/function/StringFuncTests.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.fizz.function; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.function.FuncExecutor; -import com.fizzgate.fizz.input.PathMapping; - -/** - * - * @author Francis Dong - * - */ -class StringFuncTests { - - @Test - void contextLoads() { - } - - - @Test - void testEquals() { - String funcExpression = "fn.string.equals(\"abc\", \"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testEquals2() { - String funcExpression = "fn.string.equals(null, \"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(false, result); - } - - @Test - void testEquals3() { - String funcExpression = "fn.string.equals(\"ab\\\"c\", \"abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(false, result); - } - - @Test - void testEqualsIgnoreCase() { - String funcExpression = "fn.string.equalsIgnoreCase(\"abc\", \"Abc\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(true, result); - } - - @Test - void testcompare() { - String funcExpression = "fn.string.compare(\"abc\", \"cde\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals(-1, result); - } - - @Test - void testConcat() { - String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55", result.toString()); - } - - - @Test - void testConcat2() { - String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\")"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55yyyy-MM-dd HH:mm:ss", result.toString()); - } - - @Test - void testConcat3() { - String funcExpression = "fn.string.concat(\"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\",1)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55yyyy-MM-dd HH:mm:ss1", result.toString()); - } - - @Test - void testConcatws() { - String funcExpression = "fn.string.concatws(\",\" , \"2021-07-09 22:44:55\", \"yyyy-MM-dd HH:mm:ss\" )"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55,yyyy-MM-dd HH:mm:ss", result.toString()); - } - - @Test - void testSubstring() { - String funcExpression = "fn.string.substring(\"2021-07-09 22:44:55\", 1 , 4)"; - Object result = FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55".substring(1, 4), result.toString()); - } - - @Test - void testSubstring2() { - ONode ctxNode = ONode.load(new HashMap()); - - Map m = new HashMap<>(); - m.put("a", "1"); - m.put("b", "1"); - - PathMapping.setByPath(ctxNode, "data.dateStr", "2021-07-09 22:44:55", true); - PathMapping.setByPath(ctxNode, "data.startIndex", 1, true); - PathMapping.setByPath(ctxNode, "data", m, false); - - String funcExpression = "fn.string.substring({data.dateStr}, {data.startIndex})"; -// String funcExpression = "fn.string.substring(\"2021-07-09 22:44:55\", 1)"; - Object result = FuncExecutor.getInstance().exec(ctxNode, funcExpression); - System.out.println(result); - assertEquals("2021-07-09 22:44:55".substring(1), result.toString()); - } - - @Test - void testIndexOf() { - String funcExpression = "fn.string.indexOf(\"2021-07-09 22:44:55\", \"07\")"; - int result = (int)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55".indexOf("07"), result); - } - - @Test - void testStartsWith() { - String funcExpression = "fn.string.startsWith(\"2021-07-09 22:44:55\", \"2021\")"; - boolean result = (boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55".startsWith("2021"), result); - } - - @Test - void testEndsWith() { - String funcExpression = "fn.string.endsWith(\"2021-07-09 22:44:55\", \"44:55\")"; - boolean result = (boolean)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55".endsWith("44:55"), result); - } - - @Test - void testToUpperCase() { - String funcExpression = "fn.string.toUpperCase(\"aBc\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("aBc".toUpperCase(), result); - } - - @Test - void testToLowerCase() { - String funcExpression = "fn.string.toLowerCase(\"aBc\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("aBc".toLowerCase(), result); - } - - @Test - void testToString() { - String funcExpression = "fn.string.toString(\"aBc\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("aBc", result); - } - - @Test - void testToString2() { - String funcExpression = "fn.string.toString(true)"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("true", result); - } - - @Test - void testToString3() { - String funcExpression = "fn.string.toString(234)"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("234", result); - } - - - @Test - void testToString4() { - String funcExpression = "fn.string.toString(fn.date.timestamp())"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - System.out.println(result); - } - - @Test - void testReplace() { - String funcExpression = "fn.string.replace(\"2021-07-09 22:44:55\", \"44:55\", \"00:00\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55".replace("44:55", "00:00"), result); - } - - @Test - void testReplaceAll() { - String funcExpression = "fn.string.replaceAll(\"2021-07-09 22:44:55 44:55\", \"44:55\", \"00:00\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55 44:55".replaceAll("44:55", "00:00"), result); - } - - @Test - void testReplaceAll2() { - String funcExpression = "fn.string.replaceAll(\"1.2.3\", \"\\.\", \"\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("1.2.3".replaceAll("\\.", ""), result); - } - - @Test - void testReplaceFirst() { - String funcExpression = "fn.string.replaceFirst(\"2021-07-09 22:44:55 44:55\", \"44:55\", \"00:00\")"; - String result = (String)FuncExecutor.getInstance().exec(null, funcExpression); - assertEquals("2021-07-09 22:44:55 44:55".replaceFirst("44:55", "00:00"), result); - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/group/DevTestGroup.java b/fizz-core/src/test/java/com/fizzgate/fizz/group/DevTestGroup.java deleted file mode 100644 index ed1136a..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/group/DevTestGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.fizzgate.fizz.group; - -public class DevTestGroup { -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/group/FastTestGroup.java b/fizz-core/src/test/java/com/fizzgate/fizz/group/FastTestGroup.java deleted file mode 100644 index 3ce12ef..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/group/FastTestGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.fizzgate.fizz.group; - -public class FastTestGroup { -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/group/SlowTestGroup.java b/fizz-core/src/test/java/com/fizzgate/fizz/group/SlowTestGroup.java deleted file mode 100644 index 06d07d8..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/group/SlowTestGroup.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.fizzgate.fizz.group; - -public class SlowTestGroup { -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputMockTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputMockTests.java deleted file mode 100644 index 8d01626..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputMockTests.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.fizzgate.fizz.input; - -import org.apache.dubbo.config.ReferenceConfig; -import org.apache.dubbo.rpc.service.GenericService; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.context.ConfigurableApplicationContext; - -import com.fizzgate.fizz.Step; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.StepResponse; -import com.fizzgate.fizz.group.FastTestGroup; -import com.fizzgate.fizz.input.InputContext; -import com.fizzgate.fizz.input.InputFactory; -import com.fizzgate.fizz.input.extension.dubbo.DubboInput; -import com.fizzgate.fizz.input.extension.dubbo.DubboInputConfig; -import com.fizzgate.proxy.dubbo.ApacheDubboGenericService; -import com.fizzgate.proxy.dubbo.DubboInterfaceDeclaration; - -import java.lang.ref.SoftReference; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -@Category(FastTestGroup.class) -public class DubboInputMockTests { - private static final String SERVICE_NAME = "com.fizzgate.test"; - private static final String METHOD_NAME = "method"; - private static final String[] LEFT = new String[]{}; - - private static final Object[] RIGHT = new Object[]{}; - - private ApacheDubboGenericService proxy; -// @Before -// public void setup(){ -// ApacheDubboGenericProxyTests test = new ApacheDubboGenericProxyTests(); -// proxy = test.getMockApachDubbo(); -// } - - @Test - public void test() { - DubboInterfaceDeclaration declaration = new DubboInterfaceDeclaration(); - declaration.setServiceName(SERVICE_NAME); - declaration.setMethod(METHOD_NAME); - declaration.setParameterTypes("java.lang.String, java.lang.String"); - declaration.setTimeout(3000); - - ReferenceConfig referenceConfig = mock(ReferenceConfig.class); - GenericService genericService = mock(GenericService.class); - when(referenceConfig.get()).thenReturn(genericService); - when(referenceConfig.getInterface()).thenReturn(SERVICE_NAME); - CompletableFuture future = new CompletableFuture<>(); - when(genericService.$invokeAsync(any(), any(), any())).thenReturn(future); - ApacheDubboGenericService apacheDubboProxyService = new ApacheDubboGenericService(); - ApacheDubboGenericService proxy = spy(apacheDubboProxyService); - when(proxy.createReferenceConfig(SERVICE_NAME, null, null)).thenReturn(referenceConfig); - - ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class); - when(applicationContext.getBean(ApacheDubboGenericService.class)).thenReturn(proxy); - - Step step = mock(Step.class); - when(step.getCurrentApplicationContext()).thenReturn(applicationContext); - - StepResponse stepResponse = new StepResponse(step, null, new HashMap>()); - DubboInputConfig config = mock(DubboInputConfig.class); - when(config.getServiceName()).thenReturn(SERVICE_NAME); - InputFactory.registerInput(DubboInput.TYPE, DubboInput.class); - DubboInput dubboInput = (DubboInput)InputFactory.createInput(DubboInput.TYPE.toString()); - - dubboInput.setName("input1"); - dubboInput.setWeakStep(new SoftReference<>(step)); - dubboInput.setStepResponse(stepResponse); - dubboInput.setConfig(config); - StepContext stepContext = mock(StepContext.class); - stepContext.put("step1", stepResponse); - InputContext context = new InputContext(stepContext, null); - dubboInput.beforeRun(context); - - dubboInput.run(); - - future.complete("success"); - - } -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputTests.java deleted file mode 100644 index 740c6bf..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/DubboInputTests.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.fizzgate.fizz.input; - -import org.junit.experimental.categories.Category; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import com.fizzgate.fizz.group.DevTestGroup; -import com.fizzgate.fizz.input.extension.dubbo.DubboInput; -import com.fizzgate.fizz.input.extension.dubbo.DubboInputConfig; - -import reactor.core.publisher.Mono; - -import java.util.HashMap; -import java.util.Map; - - -@ActiveProfiles("") -//@SpringBootTest -@Category(DevTestGroup.class) -class DubboInputTests { - private static final String SERVICE_NAME = "com.fizzgate.fizz.examples.dubbo.common.service.UserService"; - private static final String METHOD_NAME = "findAll"; - //@Test - public void test() { - - Map requestConfig = new HashMap(); - requestConfig.put("serviceName",SERVICE_NAME); - requestConfig.put("method",METHOD_NAME); -// requestConfig.put("parameterTypes", "java.lang.String"); - - DubboInputConfig inputConfig = new DubboInputConfig(requestConfig); - inputConfig.parse(); - DubboInput input = new DubboInput(); - input.setConfig(inputConfig); - input.beforeRun(null); - Monomono = input.run(); - Map result = mono.block(); - System.out.print(result); - Assertions.assertNotEquals(result, null, "no response"); - - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/GrpcInputMockTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/GrpcInputMockTests.java deleted file mode 100644 index 30f89a9..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/GrpcInputMockTests.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.fizzgate.fizz.input; - -import com.fizzgate.fizz.Step; -import com.fizzgate.fizz.StepContext; -import com.fizzgate.fizz.StepResponse; -import com.fizzgate.fizz.group.FastTestGroup; -import com.fizzgate.fizz.input.InputContext; -import com.fizzgate.fizz.input.InputFactory; -import com.fizzgate.fizz.input.extension.grpc.GrpcInput; -import com.fizzgate.fizz.input.extension.grpc.GrpcInputConfig; -import com.fizzgate.proxy.grpc.GrpcGenericService; -import com.fizzgate.proxy.grpc.GrpcInterfaceDeclaration; -import com.fizzgate.proxy.grpc.client.GrpcProxyClient; -import com.fizzgate.proxy.grpc.client.utils.ChannelFactory; -import com.google.common.util.concurrent.ListenableFuture; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.util.ReflectionTestUtils; - -import java.lang.ref.SoftReference; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.*; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -@Category(FastTestGroup.class) -public class GrpcInputMockTests { - private static final String URL ="localhost:8090"; - private static final String SERVICE_NAME = "com.fizzgate.test"; - private static final String METHOD_NAME = "method"; - private static final String[] LEFT = new String[]{}; - - private static final Object[] RIGHT = new Object[]{}; - - private GrpcGenericService proxy; -// @Before -// public void setup(){ -// ApacheDubboGenericProxyTests test = new ApacheDubboGenericProxyTests(); -// proxy = test.getMockApachDubbo(); -// } - - @Test - public void test() { - mockStatic(ChannelFactory.class); - GrpcInterfaceDeclaration declaration = new GrpcInterfaceDeclaration(); - declaration.setEndpoint(URL); - declaration.setServiceName(SERVICE_NAME); - declaration.setMethod(METHOD_NAME); - declaration.setTimeout(3000); - - GrpcProxyClient grpcProxyClient = mock(GrpcProxyClient.class); - - ListenableFuture future = new ListenableFuture() { - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return false; - } - - @Override - public boolean isCancelled() { - return false; - } - - @Override - public boolean isDone() { - return false; - } - - @Override - public String get() throws InterruptedException, ExecutionException { - return "result"; - } - - @Override - public String get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return null; - } - - @Override - public void addListener(Runnable runnable, Executor executor) { - - } - }; - when(grpcProxyClient.invokeMethodAsync(any(), any(), any(), any(), any())).thenReturn((ListenableFuture) future); - - GrpcGenericService grpcGenericService = new GrpcGenericService(); - ReflectionTestUtils.setField(grpcGenericService, "grpcProxyClient", grpcProxyClient); - - ConfigurableApplicationContext applicationContext = mock(ConfigurableApplicationContext.class); - when(applicationContext.getBean(GrpcGenericService.class)).thenReturn(grpcGenericService); - - Step step = mock(Step.class); - when(step.getCurrentApplicationContext()).thenReturn(applicationContext); - - StepResponse stepResponse = new StepResponse(step, null, new HashMap>()); - GrpcInputConfig config = mock(GrpcInputConfig.class); - when(config.getServiceName()).thenReturn(SERVICE_NAME); - InputFactory.registerInput(GrpcInput.TYPE, GrpcInput.class); - GrpcInput grpcInput = (GrpcInput)InputFactory.createInput(GrpcInput.TYPE.toString()); - HashMap request = new HashMap (); - request.put("url",URL); - ReflectionTestUtils.setField(grpcInput, "request", request); - grpcInput.setName("input1"); - grpcInput.setWeakStep(new SoftReference<>(step)); - grpcInput.setStepResponse(stepResponse); - grpcInput.setConfig(config); - StepContext stepContext = mock(StepContext.class); - stepContext.put("step1", stepResponse); - InputContext context = new InputContext(stepContext, null); - grpcInput.beforeRun(context); - - grpcInput.run(); - - - } -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/PathMappingTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/PathMappingTests.java deleted file mode 100644 index 0c96983..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/PathMappingTests.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.fizzgate.fizz.input; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.noear.snack.ONode; - -import com.fizzgate.fizz.input.PathMapping; -import com.fizzgate.global_resource.GlobalResourceService; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - - -class PathMappingTests { - @Test - void contextLoads() { - } - - @Test - void testSetByPath() { - - ONode target = ONode.load(new HashMap()); - - Map m = new HashMap<>(); - m.put("a", "1"); - m.put("b", "1"); - - PathMapping.setByPath(target, "data.id", "2", true); - PathMapping.setByPath(target, "data", m, false); - - assertEquals("1", target.get("data").get("a").getString()); - assertEquals("1", target.get("data").get("b").getString()); - assertEquals("2", target.get("data").get("id").getString()); - - - List list = new ArrayList<>(); - list.add("YYYY"); - PathMapping.setByPath(target, "data.zzz", list, true); - - List list2 = new ArrayList<>(); - list2.add("XXXX"); - PathMapping.setByPath(target, "data.zzz", list2, true); - - - List actualList = (List) target.get("data").get("zzz").toData(); - assertTrue(actualList.contains("YYYY")); - assertTrue(actualList.contains("XXXX")); - - - List list3 = new ArrayList<>(); - list3.add("vvvv"); - PathMapping.setByPath(target, "data.ppp", list3, true); - - - Map m3 = new HashMap<>(); - m3.put("sss", "sss"); - PathMapping.setByPath(target, "data.ppp", m3, true); - - List list4 = new ArrayList<>(); - list4.add("kkk"); - PathMapping.setByPath(target, "data.ppp", list4, true); - - List actualList2 = (List) target.get("data").get("ppp").toData(); - assertTrue(actualList2.contains("kkk")); - - - } - - - @Test - void testHandlePath() { - LinkedHashMap pathMap = new LinkedHashMap<>(); - pathMap.put("step1.request1.request.headers", "step1.requests.request1.request.headers"); - pathMap.put("step1.request1.requestHeaders", "step1.requests.request1.request.headers"); - pathMap.put("step1.requests.request1.requestHeaders", "step1.requests.request1.request.headers"); - - pathMap.put("step1.request1.request.params", "step1.requests.request1.request.params"); - pathMap.put("step1.request1.requestParams", "step1.requests.request1.request.params"); - pathMap.put("step1.requests.request1.requestParams", "step1.requests.request1.request.params"); - - pathMap.put("step1.request1.request.body", "step1.requests.request1.request.body"); - pathMap.put("step1.request1.requestBody", "step1.requests.request1.request.body"); - pathMap.put("step1.requests.request1.requestBody", "step1.requests.request1.request.body"); - - pathMap.put("step1.request1.response.headers", "step1.requests.request1.response.headers"); - pathMap.put("step1.request1.responseHeaders", "step1.requests.request1.response.headers"); - pathMap.put("step1.requests.request1.responseHeaders", "step1.requests.request1.response.headers"); - - pathMap.put("step1.request1.response.body", "step1.requests.request1.response.body"); - pathMap.put("step1.request1.responseBody", "step1.requests.request1.response.body"); - pathMap.put("step1.requests.request1.responseBody", "step1.requests.request1.response.body"); - - pathMap.put("input.requestHeaders", "input.request.headers"); - pathMap.put("input.requestParams", "input.request.params"); - pathMap.put("input.requestBody", "input.request.body"); - pathMap.put("input.responseHeaders", "input.response.headers"); - pathMap.put("input.responseBody", "input.response.body"); - - pathMap.put("step1.request1.request.headers.Aa", "step1.requests.request1.request.headers.AA"); - pathMap.put("step1.request1.response.headers.aa", "step1.requests.request1.response.headers.AA"); - pathMap.put("input.requestHeaders.aa", "input.request.headers.AA"); - pathMap.put("input.responseHeaders.aA", "input.response.headers.AA"); - - - for (Entry entry : pathMap.entrySet()) { - Assertions.assertEquals(entry.getValue(), PathMapping.handlePath(entry.getKey())); - } - } - - @Test - void testSelect() { - Map m = new HashMap<>(); - ONode ctxNode = PathMapping.toONode(m); - PathMapping.setByPath(ctxNode, "step1.requests.request1.request.headers.abc", "1", true); - PathMapping.setByPath(ctxNode, "step1.requests.request1.request.headers.name1", "ken", true); - PathMapping.setByPath(ctxNode, "input.request.headers.abc", "1", true); - PathMapping.setByPath(ctxNode, "input.request.headers.name1", "ken", true); - List vals = new ArrayList<>(); - vals.add("Ken"); - vals.add("Kelly"); - PathMapping.setByPath(ctxNode, "step1.requests.request1.request.headers.name2", vals, true); - PathMapping.setByPath(ctxNode, "input.request.headers.name2", vals, true); - - - - ONode abc = PathMapping.select(ctxNode, "step1.requests.request1.request.headers.abc"); - ONode name1 = PathMapping.select(ctxNode, "step1.requests.request1.request.headers.name1"); - ONode inputAbc = PathMapping.select(ctxNode, "input.request.headers.abc"); - ONode inputAbcName1 = PathMapping.select(ctxNode, "input.request.headers.name1"); - ONode name2 = PathMapping.select(ctxNode, "step1.requests.request1.request.headers.name2"); - ONode inputAbcName2 = PathMapping.select(ctxNode, "input.request.headers.name2"); - assertEquals("1", (String)abc.toData()); - assertEquals("ken", (String)name1.toData()); - assertEquals("1", (String)inputAbc.toData()); - assertEquals("ken", (String)inputAbcName1.toData()); - assertEquals(2, ((List)name2.toData()).size()); - assertEquals(2, ((List)inputAbcName2.toData()).size()); - - abc = PathMapping.select(ctxNode, "step1.requests.request1.request.headers.abc[0]"); - name1 = PathMapping.select(ctxNode, "step1.requests.request1.request.headers.name1[0]"); - inputAbc = PathMapping.select(ctxNode, "input.request.headers.abc[0]"); - inputAbcName1 = PathMapping.select(ctxNode, "input.request.headers.name1[0]"); - name2 = PathMapping.select(ctxNode, "step1.requests.request1.request.headers.name2[0]"); - inputAbcName2 = PathMapping.select(ctxNode, "input.request.headers.name2[0]"); - assertEquals("1", (String)abc.toData()); - assertEquals("ken", (String)name1.toData()); - assertEquals("1", (String)inputAbc.toData()); - assertEquals("ken", (String)inputAbcName1.toData()); - assertEquals("Ken", (String)name2.toData()); - assertEquals("Ken", (String)inputAbcName2.toData()); - - PathMapping.setByPath(ctxNode, "step1.requests.request1.request.headers.TEST", "1", true); - Object abcVal1 = PathMapping.getValueByPath(ctxNode, "step1.requests.request1.request.headers.test[0]|123"); - Object abcVal2 = PathMapping.getValueByPath(ctxNode, "step1.requests.request1.request.headers.test[3]|123456"); - assertEquals("1", (String)abcVal1); - assertEquals("123456", (String)abcVal2); - - } - - - @Test - void testArray() { - ONode ctxNode = ONode.load(new HashMap()); - - Map m = new HashMap<>(); - m.put("a", "1"); - m.put("b", "1"); - - List list = new ArrayList<>(); - list.add("0"); - list.add("1"); - list.add("2"); - list.add("3"); - list.add("4"); - - PathMapping.setByPath(ctxNode, "data.m", m, true); - - PathMapping.setByPath(ctxNode, "data.arr", list, true); - - Object abcVal1 = PathMapping.getValueByPath(ctxNode, "data.arr[0]"); - assertEquals("0", (String)abcVal1); - Object abcVal2 = PathMapping.getValueByPath(ctxNode, "data.arr[-1]"); - assertEquals("4", (String)abcVal2); - } - - @Test - void testGlobalResource() { - ONode resNode = ONode.load(new HashMap()); - - Map m = new HashMap<>(); - m.put("a", "1"); - m.put("b", "1"); - - List list = new ArrayList<>(); - list.add("0"); - list.add("1"); - list.add("2"); - list.add("3"); - list.add("4"); - - - PathMapping.setByPath(resNode, "data.m", m, true); - - PathMapping.setByPath(resNode, "data.arr", list, true); - - GlobalResourceService.resNode = resNode; - - ONode emptyCtx = ONode.load(new HashMap()); - - Object abcVal1 = PathMapping.getValueByPath(emptyCtx, "g.data.arr[0]"); - assertEquals("0", (String)abcVal1); - Object abcVal2 = PathMapping.getValueByPath(emptyCtx, "g.data.arr[-1]"); - assertEquals("4", (String)abcVal2); - } - -} \ No newline at end of file diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/RequestInputTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/RequestInputTests.java deleted file mode 100644 index d60aaa4..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/RequestInputTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2021 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.fizz.input; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import com.fizzgate.fizz.input.extension.request.RequestInput; - -/** - * - * @author Francis Dong - * - */ -public class RequestInputTests { - - @Test - public void testParseBody() { - - // test json and text - String[] contentTypes = new String[] { "application/json; charset=UTF-8", "application/json;", - "application/json", "text/plain" }; - - String[] respBodys = new String[] { "{\"a\":\"b\"}", "[{\"a\":\"b\"}]" }; - - RequestInput requestInput = new RequestInput(); - for (int n = 0; n < respBodys.length; n++) { - String respBody = respBodys[n]; - for (int i = 0; i < contentTypes.length; i++) { - String ct = contentTypes[i]; - Object body = requestInput.parseBody(ct, respBody); - assertEquals(respBody, body.toString()); - } - } - - // test invalid text content - contentTypes = new String[] { "text/plain" }; - respBodys = new String[] { "{\"a\":\"b\"", "{\"a\":\"b\"}]" }; - for (int n = 0; n < respBodys.length; n++) { - String respBody = respBodys[n]; - for (int i = 0; i < contentTypes.length; i++) { - String ct = contentTypes[i]; - Object body = requestInput.parseBody(ct, respBody); - assertEquals(respBody, body.toString()); - } - } - - // test html js - contentTypes = new String[] {"application/javascript", "text/html"}; - respBodys = new String[] { "{\"a\":\"b\"", "{\"a\":\"b\"}]", "var a=1;" , "" }; - for (int n = 0; n < respBodys.length; n++) { - String respBody = respBodys[n]; - for (int i = 0; i < contentTypes.length; i++) { - String ct = contentTypes[i]; - Object body = requestInput.parseBody(ct, respBody); - assertEquals(respBody, body.toString()); - } - } - - // test xml - String xmlBody = "123"; - Map> root = (Map>) requestInput - .parseBody("application/xml", xmlBody); - Map m = root.get("person"); - assertEquals("b", m.get("-a")); - assertEquals("123", m.get("#text")); - } - -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceMockTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceMockTests.java deleted file mode 100644 index 37548f6..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceMockTests.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.fizzgate.fizz.input.proxy.dubbo; - -import org.apache.dubbo.config.ReferenceConfig; -import org.apache.dubbo.rpc.service.GenericService; - -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -import com.fizzgate.fizz.group.FastTestGroup; -import com.fizzgate.proxy.dubbo.ApacheDubboGenericService; -import com.fizzgate.proxy.dubbo.DubboInterfaceDeclaration; - -import java.util.HashMap; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) -@Category(FastTestGroup.class) -public class ApacheDubboGenericServiceMockTests { - private static final String SERVICE_NAME = "com.fizzgate.test"; - private static final String METHOD_NAME = "method"; - private static final String[] LEFT = new String[]{}; - - private static final Object[] RIGHT = new Object[]{}; - @Before - public void setup(){ - - } - - public ApacheDubboGenericService getMockApachDubbo(){ - ReferenceConfig referenceConfig = mock(ReferenceConfig.class); - GenericService genericService = mock(GenericService.class); - when(referenceConfig.get()).thenReturn(genericService); - when(referenceConfig.getInterface()).thenReturn(SERVICE_NAME); - ApacheDubboGenericService apacheDubboProxyService = mock(ApacheDubboGenericService.class); - when(apacheDubboProxyService.createReferenceConfig(SERVICE_NAME, null, null)).thenReturn(referenceConfig); - CompletableFuture future = new CompletableFuture<>(); - when(genericService.$invokeAsync(METHOD_NAME, LEFT, RIGHT)).thenReturn(future); - future.complete("success"); - return apacheDubboProxyService; - } - @Test - public void test() { - HashMap attachments = mock(HashMap.class); - DubboInterfaceDeclaration declaration = mock(DubboInterfaceDeclaration.class); - declaration.setServiceName(SERVICE_NAME); - declaration.setMethod(METHOD_NAME); - declaration.setParameterTypes("java.lang.String, java.lang.String"); - declaration.setTimeout(3000); - ApacheDubboGenericServiceMockTests test = new ApacheDubboGenericServiceMockTests(); - ApacheDubboGenericService apacheDubboProxyService = test.getMockApachDubbo(); - apacheDubboProxyService.send(null, declaration, attachments); - } - -} diff --git a/fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceTests.java b/fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceTests.java deleted file mode 100644 index 3de5d90..0000000 --- a/fizz-core/src/test/java/com/fizzgate/fizz/input/proxy/dubbo/ApacheDubboGenericServiceTests.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.fizzgate.fizz.input.proxy.dubbo; - -import org.apache.dubbo.config.ReferenceConfig; -import org.apache.dubbo.rpc.service.GenericService; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -import com.fizzgate.proxy.dubbo.ApacheDubboGenericService; -import com.fizzgate.proxy.dubbo.DubboInterfaceDeclaration; - -import java.util.HashMap; -import java.util.concurrent.CompletableFuture; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) -public class ApacheDubboGenericServiceTests { - private static final String SERVICE_NAME = "com.fizzgate.test"; - private static final String METHOD_NAME = "method"; - private static final String[] LEFT = new String[]{}; - - private static final Object[] RIGHT = new Object[]{}; - @Before - public void setup(){ - - } - - public ApacheDubboGenericService getMockApachDubbo(){ - ReferenceConfig referenceConfig = mock(ReferenceConfig.class); - GenericService genericService = mock(GenericService.class); - when(referenceConfig.get()).thenReturn(genericService); - when(referenceConfig.getInterface()).thenReturn(SERVICE_NAME); - ApacheDubboGenericService apacheDubboProxyService = mock(ApacheDubboGenericService.class); - when(apacheDubboProxyService.createReferenceConfig(SERVICE_NAME, null, null)).thenReturn(referenceConfig); - CompletableFuture future = new CompletableFuture<>(); - when(genericService.$invokeAsync(METHOD_NAME, LEFT, RIGHT)).thenReturn(future); - future.complete("success"); - return apacheDubboProxyService; - } - @Test - public void test() { - HashMap attachments = mock(HashMap.class); - DubboInterfaceDeclaration declaration = mock(DubboInterfaceDeclaration.class); - declaration.setServiceName(SERVICE_NAME); - declaration.setMethod(METHOD_NAME); - declaration.setParameterTypes("java.lang.String, java.lang.String"); - declaration.setTimeout(3000); - ApacheDubboGenericServiceTests test = new ApacheDubboGenericServiceTests(); - ApacheDubboGenericService apacheDubboProxyService = test.getMockApachDubbo(); - apacheDubboProxyService.send(null, declaration, attachments); - } - -} diff --git a/fizz-core/src/test/java/com/fizzgate/proxy/CallbackServiceTests.java b/fizz-core/src/test/java/com/fizzgate/proxy/CallbackServiceTests.java index b0e16e4..3c7b6d0 100644 --- a/fizz-core/src/test/java/com/fizzgate/proxy/CallbackServiceTests.java +++ b/fizz-core/src/test/java/com/fizzgate/proxy/CallbackServiceTests.java @@ -1,5 +1,6 @@ package com.fizzgate.proxy; +import com.fizzgate.aggregate.web.service.AggregateService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.core.io.buffer.DataBuffer; @@ -12,7 +13,6 @@ import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeStrategies; -import com.fizzgate.fizz.AggregateService; import com.fizzgate.plugin.auth.ApiConfig; import com.fizzgate.plugin.auth.ApiConfigService; import com.fizzgate.plugin.auth.CallbackConfig; diff --git a/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java b/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java index 655c993..ca2d909 100644 --- a/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java +++ b/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java @@ -1,12 +1,9 @@ package com.fizzgate.plugin.grayrelease; import com.fasterxml.jackson.core.type.TypeReference; -import com.fizzgate.filter.AggregateFilter; import com.fizzgate.filter.FilterResult; -import com.fizzgate.fizz.ConfigLoader; import com.fizzgate.plugin.FizzPluginFilterChain; import com.fizzgate.plugin.auth.ApiConfig; -import com.fizzgate.plugin.grayrelease.GrayReleasePlugin; import com.fizzgate.proxy.Route; import com.fizzgate.util.Consts; import com.fizzgate.util.JacksonUtils; diff --git a/fizz-spring-boot-starter/src/main/resources/META-INF/spring.factories b/fizz-spring-boot-starter/src/main/resources/META-INF/spring.factories index dcba522..ee1636c 100644 --- a/fizz-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ b/fizz-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -1,7 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.fizzgate.config.AggregateRedisConfig,\ com.fizzgate.config.ApolloConfig,\ -com.fizzgate.config.AppConfigProperties,\ com.fizzgate.config.FlowControlConfig,\ com.fizzgate.config.FlowStatSchedConfig,\ com.fizzgate.config.ProxyWebClientConfig,\ @@ -15,17 +14,12 @@ com.fizzgate.controller.CallbackController,\ com.fizzgate.controller.ConfigController,\ com.fizzgate.controller.FlowControlController,\ com.fizzgate.controller.ManagerConfigController,\ -com.fizzgate.filter.AggregateFilter,\ com.fizzgate.filter.CallbackFilter,\ com.fizzgate.filter.CorsFilterConfig,\ -com.fizzgate.filter.FilterExceptionHandlerConfig,\ com.fizzgate.filter.FizzLogFilter,\ com.fizzgate.filter.FlowControlFilter,\ com.fizzgate.filter.PreprocessFilter,\ com.fizzgate.filter.RouteFilter,\ -com.fizzgate.fizz.AggregateService,\ -com.fizzgate.fizz.ConfigLoader,\ -com.fizzgate.listener.AggregateChannelListener,\ com.fizzgate.plugin.auth.ApiConfigService,\ com.fizzgate.plugin.auth.ApiConfig2appsService,\ com.fizzgate.plugin.auth.AppService,\ From 5734b6e38b9ac040aed6bf98a8ba5a0ebfcc0606 Mon Sep 17 00:00:00 2001 From: linwaiwai Date: Sun, 4 Jun 2023 13:46:11 +0800 Subject: [PATCH 03/14] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A7=81=E6=9C=89?= =?UTF-8?q?=E4=BB=93=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fizz-core/pom.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index 53935a1..b2d707d 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -334,6 +334,28 @@ true + + + rdc-releases + https://packages.aliyun.com/maven/repository/2149645-release-bYG3kG/ + + true + + + false + + + + rdc-snapshots + https://packages.aliyun.com/maven/repository/2149645-snapshot-1twqZE/ + + false + + + true + + + From 2830d6ab91bf4ae639f7d3539eeb91bae88eefe6 Mon Sep 17 00:00:00 2001 From: linwaiwai Date: Sun, 4 Jun 2023 17:36:50 +0800 Subject: [PATCH 04/14] =?UTF-8?q?=E6=8E=92=E9=99=A4=E6=8E=89fizz-core?= =?UTF-8?q?=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fizz-core/pom.xml | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index b2d707d..f227aee 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -21,6 +21,12 @@ com.fizzgate fizz-aggregate-spring-boot-starter ${aggregate.version} + + + com.fizzgate + fizz-core + + @@ -334,28 +340,6 @@ true - - - rdc-releases - https://packages.aliyun.com/maven/repository/2149645-release-bYG3kG/ - - true - - - false - - - - rdc-snapshots - https://packages.aliyun.com/maven/repository/2149645-snapshot-1twqZE/ - - false - - - true - - - From f26be466852fff62ab90ee47582c6c490c5f8947 Mon Sep 17 00:00:00 2001 From: linwaiwai Date: Sun, 4 Jun 2023 13:46:11 +0800 Subject: [PATCH 05/14] add private repo --- fizz-core/pom.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index 53935a1..b2d707d 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -334,6 +334,28 @@ true + + + rdc-releases + https://packages.aliyun.com/maven/repository/2149645-release-bYG3kG/ + + true + + + false + + + + rdc-snapshots + https://packages.aliyun.com/maven/repository/2149645-snapshot-1twqZE/ + + false + + + true + + + From 2722235d895c8c01eeea0e403760ff2fdba02705 Mon Sep 17 00:00:00 2001 From: linwaiwai Date: Sun, 4 Jun 2023 17:36:50 +0800 Subject: [PATCH 06/14] exclude fizz-core --- fizz-core/pom.xml | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index b2d707d..f227aee 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -21,6 +21,12 @@ com.fizzgate fizz-aggregate-spring-boot-starter ${aggregate.version} + + + com.fizzgate + fizz-core + + @@ -334,28 +340,6 @@ true - - - rdc-releases - https://packages.aliyun.com/maven/repository/2149645-release-bYG3kG/ - - true - - - false - - - - rdc-snapshots - https://packages.aliyun.com/maven/repository/2149645-snapshot-1twqZE/ - - false - - - true - - - From 65b79a0221b6cef1ae8e518caf88a19a3612d2ee Mon Sep 17 00:00:00 2001 From: "nil.zhong" Date: Tue, 6 Jun 2023 16:11:12 +0800 Subject: [PATCH 07/14] Remove local repo & json-schema-validator-i18n-support --- fizz-bootstrap/js/common.js | 273 ------------------ fizz-bootstrap/pom.xml | 19 -- fizz-common/pom.xml | 40 --- .../com/fizzgate/util/JsonSchemaUtils.java | 181 ------------ .../fizzgate/util/JsonSchemaUtilsTest.java | 67 ----- fizz-core/pom.xml | 17 -- fizz-plugin/pom.xml | 6 - .../grayrelease/GrayReleasePluginTests.java | 4 +- fizz-spring-boot-starter/pom.xml | 6 - pom.xml | 6 - ...schema-validator-i18n-support-1.0.39_6.jar | Bin 195706 -> 0 bytes ...schema-validator-i18n-support-1.0.39_6.pom | 76 ----- .../maven-metadata-local.xml | 12 - 13 files changed, 3 insertions(+), 704 deletions(-) delete mode 100644 fizz-bootstrap/js/common.js delete mode 100644 fizz-common/src/main/java/com/fizzgate/util/JsonSchemaUtils.java delete mode 100644 fizz-common/src/test/java/com/fizzgate/util/JsonSchemaUtilsTest.java delete mode 100644 repo/com/networknt/json-schema-validator-i18n-support/1.0.39_6/json-schema-validator-i18n-support-1.0.39_6.jar delete mode 100644 repo/com/networknt/json-schema-validator-i18n-support/1.0.39_6/json-schema-validator-i18n-support-1.0.39_6.pom delete mode 100644 repo/com/networknt/json-schema-validator-i18n-support/maven-metadata-local.xml diff --git a/fizz-bootstrap/js/common.js b/fizz-bootstrap/js/common.js deleted file mode 100644 index c8eb437..0000000 --- a/fizz-bootstrap/js/common.js +++ /dev/null @@ -1,273 +0,0 @@ -/** - * context 上下文便捷操作函数 - * - */ -var common = { - /* *********** private function begin *********** */ - - /** - * 获取上下文中客户端请求对象 - * @param {*} ctx 上下文 【必填】 - */ - getInputReq: function (ctx){ - if(!ctx || !ctx['input'] || !ctx['input']['request']){ - return {}; - } - return ctx['input']['request'] - }, - - /** - * 获取上下文步骤中请求接口的请求对象 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名称 【必填】 - * @param {*} requestName 请求名称 【必填】 - */ - getStepReq: function (ctx, stepName, requestName){ - if(!ctx || !stepName || !requestName){ - return {}; - } - if(!ctx[stepName] || !ctx[stepName]['requests'] || !ctx[stepName]['requests'][requestName] || - !ctx[stepName]['requests'][requestName]['request']){ - return {}; - } - return ctx[stepName]['requests'][requestName]['request']; - }, - - /** - * 获取上下文步骤中请求接口的响应对象 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名称 【必填】 - * @param {*} requestName 请求名称 【必填】 - */ - getStepResp: function (ctx, stepName, requestName){ - if(!ctx || !stepName || !requestName){ - return {}; - } - if(!ctx[stepName] || !ctx[stepName]['requests'] || !ctx[stepName]['requests'][requestName] || - !ctx[stepName]['requests'][requestName]['response']){ - return {}; - } - return ctx[stepName]['requests'][requestName]['response']; - }, - - /* *********** private function end *********** */ - - /* *********** input begin ************ */ - - /** - * 获取客户端请求头 - * @param {*} ctx 上下文 【必填】 - * @param {*} headerName 请求头字段名 【选填】,不传时返回所有请求头 - */ - getInputReqHeader: function (ctx, headerName){ - var req = this.getInputReq(ctx); - var headers = req['headers'] || {}; - return headerName ? headers[headerName.toUpperCase()] : headers; - }, - - /** - * 获取客户端URL请求参数(query string) - * @param {*} ctx 上下文 【必填】 - * @param {*} paramName URL参数名 【选填】,不传时返回所有请求参数 - */ - getInputReqParam: function (ctx, paramName){ - var req = this.getInputReq(ctx); - var params = req['params'] || {}; - return paramName ? params[paramName] : params; - }, - - /** - * 获取客户端请求体 - * @param {*} ctx 上下文 【必填】 - * @param {*} field 字段名 【选填】,不传时返回整个请求体 - */ - getInputReqBody: function (ctx, field){ - var req = this.getInputReq(ctx); - var body = req['body'] || {}; - return field ? body[field] : body; - }, - - /** - * 获取返回给客户端的响应头 - * @param {*} ctx 上下文 【必填】 - * @param {*} headerName 响应头字段名 【选填】,不传时返回所有响应头 - */ - getInputRespHeader: function (ctx, headerName){ - var req = this.getInputReq(ctx); - var headers = req['headers'] || {}; - return headerName ? headers[headerName.toUpperCase()] : headers; - }, - - /** - * 获取返回给客户端的响应体 - * @param {*} ctx 上下文 【必填】 - * @param {*} field 字段名 【选填】,不传时返回整个响应体 - */ - getInputRespBody: function (ctx, field){ - var req = this.getInputReq(ctx); - var body = req['body'] || {}; - return field ? body[field] : body; - }, - - /* *********** input begin ************ */ - - /* *********** step request begin ************ */ - - /** - * 获取步骤中调用的接口的请求头 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - * @param {*} requestName 请求的接口名 【必填】 - * @param {*} headerName 请求头字段名 【选填】,不传时返回所有请求头 - */ - getStepReqHeader: function (ctx, stepName, requestName, headerName){ - var req = this.getStepReq(ctx, stepName, requestName); - var headers = req['headers'] || {}; - return headerName ? headers[headerName.toUpperCase()] : headers; - }, - - /** - * 获取步骤中调用的接口的URL参数 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - * @param {*} requestName 请求的接口名 【必填】 - * @param {*} paramName URL参数名 【选填】,不传时返回所有URL参数 - */ - getStepReqParam: function (ctx, stepName, requestName, paramName){ - var req = this.getStepReq(ctx, stepName, requestName); - var params = req['params'] || {}; - return paramName ? params[paramName] : params; - }, - - /** - * 获取步骤中调用的接口的请求体 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - * @param {*} requestName 请求的接口名 【必填】 - * @param {*} field 字段名 【选填】,不传时返回整个请求体 - */ - getStepReqBody: function (ctx, stepName, requestName, field){ - var req = this.getStepReq(ctx, stepName, requestName); - var body = req['body'] || {}; - return field ? body[field] : body; - }, - - /** - * 获取步骤中调用的接口的响应头 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - * @param {*} requestName 请求的接口名 【必填】 - * @param {*} headerName 响应头字段名 【选填】,不传时返回所有响应头 - */ - getStepRespHeader: function (ctx, stepName, requestName, headerName){ - var resp = this.getStepResp(ctx, stepName, requestName); - var headers = resp['headers'] || {}; - return headerName ? headers[headerName.toUpperCase()] : headers; - }, - - /** - * 获取步骤中调用的接口的响应体 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - * @param {*} requestName 请求的接口名 【必填】 - * @param {*} field 字段名 【选填】,不传时返回整个响应体 - */ - getStepRespBody: function (ctx, stepName, requestName, field){ - var resp = this.getStepResp(ctx, stepName, requestName); - var body = resp['body'] || {}; - return field ? body[field] : body; - }, - - /** - * 获取步骤结果 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - * @param {*} field 字段名 【选填】,不传时返回整个步骤结果对象 - */ - getStepResult: function (ctx, stepName, field){ - if(!ctx || !stepName || !ctx[stepName]){ - return {}; - } - var result = ctx[stepName]['result'] || {}; - return field ? result[field] : result; - }, - - /** - * 获取步骤循环结果 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - */ - getStepCircle: function (ctx, stepName){ - if(!ctx || !stepName || !ctx[stepName]){ - return null; - } - // 返回循环结果数组 - return ctx[stepName]['circle']; - }, - - /** - * 获取请求的循环结果 - * @param {*} ctx 上下文 【必填】 - * @param {*} stepName 步骤名【必填】 - */ - getRequestCircle: function (ctx, stepName, requestName){ - if(!ctx || !stepName || !requestName){ - return null; - } - if(!ctx[stepName] || !ctx[stepName]['requests'] || !ctx[stepName]['requests'][requestName] || - !ctx[stepName]['requests'][requestName]['request']){ - return null; - } - // 返回循环结果数组 - return ctx[stepName]['requests'][requestName]['circle']; - } - - /* *********** step request end ************ */ - - ,/** - ** 乘法函数,用来得到精确的乘法结果 - ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。 - ** 调用:accMul(arg1,arg2) - ** 返回值:arg1乘以 arg2的精确结果 - **/ - accMul:function (arg1, arg2) { - var m = 0, s1 = arg1.toString(), s2 = arg2.toString(); - try { - m += s1.split(".")[1].length; - } catch (e) { - } - try { - m += s2.split(".")[1].length; - } catch (e) { - } - return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) - / Math.pow(10, m); - }, - - /** - ** 除法函数,用来得到精确的除法结果 - ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。 - ** 调用:accDiv(arg1,arg2) - ** 返回值:arg1除以arg2的精确结果 - **/ - accDiv:function (arg1, arg2) { - var t1 = 0, t2 = 0, r1, r2; - try { - t1 = arg1.toString().split(".")[1].length; - } catch (e) { - } - try { - t2 = arg2.toString().split(".")[1].length; - } catch (e) { - } - with (Math) { - r1 = Number(arg1.toString().replace(".", "")); - r2 = Number(arg2.toString().replace(".", "")); - return (r1 / r2) * pow(10, t2 - t1); - } - } - - -}; - - diff --git a/fizz-bootstrap/pom.xml b/fizz-bootstrap/pom.xml index 13ebd0e..48ae0d3 100644 --- a/fizz-bootstrap/pom.xml +++ b/fizz-bootstrap/pom.xml @@ -57,12 +57,6 @@ ${project.version} - - com.networknt - json-schema-validator-i18n-support - 1.0.39_6 - - - - - repo - file://${project.basedir}/../repo - - - @@ -120,12 +107,6 @@ spring-boot-maven-plugin true - - - com.networknt - json-schema-validator-i18n-support - - diff --git a/fizz-common/pom.xml b/fizz-common/pom.xml index 4ac6205..a73096b 100644 --- a/fizz-common/pom.xml +++ b/fizz-common/pom.xml @@ -41,12 +41,6 @@ spring-cloud-commons - - com.networknt - json-schema-validator-i18n-support - 1.0.39_6 - - org.jruby.joni joni @@ -136,46 +130,12 @@ - - - repo - file://${project.basedir}/../repo - - - org.apache.maven.plugins maven-source-plugin - - maven-shade-plugin - 3.2.4 - - false - true - - - com.networknt:json-schema-validator-i18n-support - - - - - com.networknt - com.fizzgate.repackaged.com.networknt - - - - - - package - - shade - - - - \ No newline at end of file diff --git a/fizz-common/src/main/java/com/fizzgate/util/JsonSchemaUtils.java b/fizz-common/src/main/java/com/fizzgate/util/JsonSchemaUtils.java deleted file mode 100644 index 1625391..0000000 --- a/fizz-common/src/main/java/com/fizzgate/util/JsonSchemaUtils.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.fizzgate.util; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.networknt.schema.JsonSchema; -import com.networknt.schema.JsonSchemaFactory; -import com.networknt.schema.SchemaValidatorsConfig; -import com.networknt.schema.SpecVersion; -import com.networknt.schema.ValidationMessage; -import com.networknt.schema.ValidatorTypeCode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; - -import java.lang.reflect.Field; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * JSON Schema工具类 - * @author zhongjie - */ -public class JsonSchemaUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(JsonSchemaUtils.class); - static { - try { - // 替换验证信息提示 - Field messageFormatField = ValidatorTypeCode.class.getDeclaredField("messageFormat"); - //忽略属性的访问权限 - messageFormatField.setAccessible(true); - messageFormatField.set(ValidatorTypeCode.ADDITIONAL_PROPERTIES, new MessageFormat( - "{1}在schema中没有定义并且schema不允许指定外的字段")); - messageFormatField.set(ValidatorTypeCode.ALL_OF, new MessageFormat("should be valid to all the schemas {1}")); - messageFormatField.set(ValidatorTypeCode.ANY_OF, new MessageFormat("should be valid to any of the schemas {1}")); - messageFormatField.set(ValidatorTypeCode.CROSS_EDITS, new MessageFormat("has an error with 'cross edits'")); - messageFormatField.set(ValidatorTypeCode.DEPENDENCIES, new MessageFormat("has an error with dependencies {1}")); - messageFormatField.set(ValidatorTypeCode.EDITS, new MessageFormat("has an error with 'edits'")); - messageFormatField.set(ValidatorTypeCode.ENUM, new MessageFormat("值不在限定{1}内")); - messageFormatField.set(ValidatorTypeCode.FORMAT, new MessageFormat("不符合{1}格式{2}")); - messageFormatField.set(ValidatorTypeCode.ITEMS, new MessageFormat("在索引[{1}]处为找到验证器")); - messageFormatField.set(ValidatorTypeCode.MAXIMUM, new MessageFormat("给定值应当小于等于{1}")); - messageFormatField.set(ValidatorTypeCode.MAX_ITEMS, new MessageFormat("数组至多含有{1}个元素")); - messageFormatField.set(ValidatorTypeCode.MAX_LENGTH, new MessageFormat("长度应当最多{1}")); - messageFormatField.set(ValidatorTypeCode.MAX_PROPERTIES, new MessageFormat("对象最多有{1}个字段")); - messageFormatField.set(ValidatorTypeCode.MINIMUM, new MessageFormat("给定值应当大于等于{1}")); - messageFormatField.set(ValidatorTypeCode.MIN_ITEMS, new MessageFormat("数组至少含有{1}个元素")); - messageFormatField.set(ValidatorTypeCode.MIN_LENGTH, new MessageFormat("长度应当最少{1}")); - messageFormatField.set(ValidatorTypeCode.MIN_PROPERTIES, new MessageFormat("{0}:对象最少有{1}个字段")); - messageFormatField.set(ValidatorTypeCode.MULTIPLE_OF, new MessageFormat("数值类型应当是{1}")); - messageFormatField.set(ValidatorTypeCode.NOT_ALLOWED, new MessageFormat("{1}不允许出现在数据中")); - messageFormatField.set(ValidatorTypeCode.NOT, new MessageFormat("should not be valid to the schema {1}")); - messageFormatField.set(ValidatorTypeCode.ONE_OF, new MessageFormat("should be valid to one and only one of the schemas {1}")); - messageFormatField.set(ValidatorTypeCode.PATTERN_PROPERTIES, new MessageFormat("has some error with 'pattern properties'")); - messageFormatField.set(ValidatorTypeCode.PATTERN, new MessageFormat("应当符合格式\"{1}\"")); - messageFormatField.set(ValidatorTypeCode.PROPERTIES, new MessageFormat("对象字段存在错误")); - messageFormatField.set(ValidatorTypeCode.READ_ONLY, new MessageFormat("is a readonly field, it cannot be changed")); - messageFormatField.set(ValidatorTypeCode.REF, new MessageFormat("has an error with 'refs'")); - messageFormatField.set(ValidatorTypeCode.REQUIRED, new MessageFormat("{1}字段不能为空")); - messageFormatField.set(ValidatorTypeCode.TYPE, new MessageFormat("预期类型是{2},但实际是{1}")); - messageFormatField.set(ValidatorTypeCode.UNION_TYPE, new MessageFormat("预期类型是{2},但实际是{1}")); - messageFormatField.set(ValidatorTypeCode.UNIQUE_ITEMS, new MessageFormat("数组元素唯一性冲突")); - messageFormatField.set(ValidatorTypeCode.DATETIME, new MessageFormat("{1}不是一个有效的{2}")); - messageFormatField.set(ValidatorTypeCode.UUID, new MessageFormat("{1}不是一个有效的{2}")); - messageFormatField.set(ValidatorTypeCode.ID, new MessageFormat("{1} is an invalid segment for URI {2}")); - messageFormatField.set(ValidatorTypeCode.EXCLUSIVE_MAXIMUM, new MessageFormat("给定值应当小于{1}")); - messageFormatField.set(ValidatorTypeCode.EXCLUSIVE_MINIMUM, new MessageFormat("给定值应当大于{1}")); - messageFormatField.set(ValidatorTypeCode.FALSE, new MessageFormat("Boolean schema false is not valid")); - messageFormatField.set(ValidatorTypeCode.CONST, new MessageFormat("值应当是一个常量{1}")); - messageFormatField.set(ValidatorTypeCode.CONTAINS, new MessageFormat("没有包含元素能够通过验证:{1}")); - } catch (Exception e) { - LOGGER.warn("替换ValidatorTypeCode.messageFormat异常", e); - } - } - - private JsonSchemaUtils() {} - - /** - * 验证JSON字符串是否符合JSON Schema要求 - * @param jsonSchema JSON Schema - * @param inputJson JSON字符串 - * @return null:验证通过,List:报错信息列表 - */ - public static List validate(String jsonSchema, String inputJson) { - return internalValidate(jsonSchema, inputJson, Boolean.FALSE); - } - - /** - * 验证JSON字符串是否符合JSON Schema要求,允许数字\布尔类型 是字符串格式 - * @param jsonSchema JSON Schema - * @param inputJson JSON字符串 - * @return null:验证通过,List:报错信息列表 - */ - public static List validateAllowValueStr(String jsonSchema, String inputJson) { - return internalValidate(jsonSchema, inputJson, Boolean.TRUE); - } - - private static List internalValidate(String jsonSchema, String inputJson, boolean typeLoose) { - CheckJsonResult checkJsonResult = checkJson(jsonSchema, inputJson, typeLoose); - if (checkJsonResult.errorList != null) { - return checkJsonResult.errorList; - } - - Set validationMessageSet = checkJsonResult.schema.validate(checkJsonResult.json); - if (CollectionUtils.isEmpty(validationMessageSet)) { - return null; - } - - return validationMessageSet.stream().map(validationMessage -> { - String message = validationMessage.getMessage(); - if (message != null) { - return message; - } - return validationMessage.getCode(); - }).collect(Collectors.toList()); - } - - private static CheckJsonResult checkJson(String jsonSchema, String inputJson, boolean typeLoose) { - CheckJsonResult checkJsonResult = new CheckJsonResult(); - try { - checkJsonResult.schema = getJsonSchemaFromStringContent(jsonSchema, typeLoose); - } catch (Exception e) { - checkJsonResult.errorList = new ArrayList<>(1); - checkJsonResult.errorList.add(String.format("JSON Schema格式错误,提示信息[%s]", e.getMessage())); - return checkJsonResult; - } - - try { - checkJsonResult.json = getJsonNodeFromStringContent(inputJson); - } catch (Exception e) { - checkJsonResult.errorList = new ArrayList<>(1); - checkJsonResult.errorList.add(String.format("待验证JSON格式错误,提示信息[%s]", e.getMessage())); - return checkJsonResult; - } - - return checkJsonResult; - } - - private static class CheckJsonResult { - JsonSchema schema; - JsonNode json; - List errorList; - } - - private static final JsonSchemaFactory JSON_SCHEMA_FACTORY = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); - private static final SchemaValidatorsConfig CONFIG_WITH_TYPE_LOOSE; - private static final SchemaValidatorsConfig CONFIG_WITHOUT_TYPE_LOOSE; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - static { - CONFIG_WITH_TYPE_LOOSE = new SchemaValidatorsConfig(); - CONFIG_WITH_TYPE_LOOSE.setTypeLoose(Boolean.TRUE); - CONFIG_WITHOUT_TYPE_LOOSE = new SchemaValidatorsConfig(); - CONFIG_WITHOUT_TYPE_LOOSE.setTypeLoose(Boolean.FALSE); - } - private static JsonSchema getJsonSchemaFromStringContent(String schemaContent, boolean typeLoose) { - SchemaValidatorsConfig config = typeLoose ? CONFIG_WITH_TYPE_LOOSE : CONFIG_WITHOUT_TYPE_LOOSE; - return JSON_SCHEMA_FACTORY.getSchema(schemaContent, config); - } - - private static JsonNode getJsonNodeFromStringContent(String content) throws Exception { - return OBJECT_MAPPER.readTree(content); - } -} diff --git a/fizz-common/src/test/java/com/fizzgate/util/JsonSchemaUtilsTest.java b/fizz-common/src/test/java/com/fizzgate/util/JsonSchemaUtilsTest.java deleted file mode 100644 index 5a6b705..0000000 --- a/fizz-common/src/test/java/com/fizzgate/util/JsonSchemaUtilsTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2020 the original author or authors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.fizzgate.util; - -import org.junit.jupiter.api.Test; -import com.fizzgate.schema.util.I18nUtils; -import java.util.List; -import java.util.Locale; - -import static org.junit.jupiter.api.Assertions.*; - -class JsonSchemaUtilsTest { - - @Test - void validateRequiredPropertyWithoutAssignedTitleAndTitleEn() { - I18nUtils.setContextLocale(new Locale("zh")); - try { - List validateList = JsonSchemaUtils.validate( - "{\n" + - " \"properties\": {\n" + - " \"library\": {\n" + - " \"type\": \"object\",\n" + - " \"required\": [\n" + - " \"person\"\n" + - " ],\n" + - " \"properties\": {\n" + - " \"person\": {\n" + - " \"type\": \"string\"\n" + - " }\n" + - " }\n" + - " }\n" + - " },\n" + - " \"required\": [\n" + - " \"library\"\n" + - " ],\n" + - " \"type\": [\n" + - " \"object\",\n" + - " \"null\"\n" + - " ]\n" + - "}", - "{\n" + - " \"library\":{\n" + - " }\n" + - "}"); - assertNotNull(validateList); - assertEquals(1, validateList.size()); - assertEquals("person不能为空", validateList.get(0)); - } finally { - I18nUtils.removeContextLocale(); - } - } -} \ No newline at end of file diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index f227aee..76aac1f 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -34,22 +34,9 @@ disruptor - - com.networknt - json-schema-validator-i18n-support - 1.0.39_6 - provided - - com.fizzgate fizz-common - - - com.networknt - json-schema-validator-i18n-support - - @@ -328,10 +315,6 @@ - - repo - file://${project.basedir}/../repo - sonatype-snapshots SonaType Snapshots diff --git a/fizz-plugin/pom.xml b/fizz-plugin/pom.xml index ce1b3ad..e85db8e 100644 --- a/fizz-plugin/pom.xml +++ b/fizz-plugin/pom.xml @@ -19,12 +19,6 @@ com.fizzgate fizz-common - - - com.networknt - json-schema-validator-i18n-support - - com.fizzgate diff --git a/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java b/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java index ca2d909..5a8dd6d 100644 --- a/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java +++ b/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java @@ -1,6 +1,8 @@ package com.fizzgate.plugin.grayrelease; import com.fasterxml.jackson.core.type.TypeReference; +import com.fizzgate.aggregate.web.filter.AggregateFilter; +import com.fizzgate.aggregate.web.loader.ConfigLoader; import com.fizzgate.filter.FilterResult; import com.fizzgate.plugin.FizzPluginFilterChain; import com.fizzgate.plugin.auth.ApiConfig; @@ -141,7 +143,7 @@ public class GrayReleasePluginTests { @Test public void aggregateBackendTest() { - AggregateFilter aggregateFilter = new AggregateFilter(); + AggregateFilter aggregateFilter = new AggregateFilter(null, null, null); ConfigLoader configLoader = mock(ConfigLoader.class); when( configLoader.matchAggregateResource("GET", "/_proxytest/bservice/bpath/xxx") diff --git a/fizz-spring-boot-starter/pom.xml b/fizz-spring-boot-starter/pom.xml index 9be4d4b..ed6769b 100644 --- a/fizz-spring-boot-starter/pom.xml +++ b/fizz-spring-boot-starter/pom.xml @@ -245,12 +245,6 @@ com.fizzgate fizz-common - - - com.networknt - json-schema-validator-i18n-support - - com.fizzgate diff --git a/pom.xml b/pom.xml index a7a8cee..a81405a 100644 --- a/pom.xml +++ b/pom.xml @@ -98,12 +98,6 @@ com.fizzgate fizz-common ${project.version} - - - com.networknt - json-schema-validator-i18n-support - - com.fizzgate diff --git a/repo/com/networknt/json-schema-validator-i18n-support/1.0.39_6/json-schema-validator-i18n-support-1.0.39_6.jar b/repo/com/networknt/json-schema-validator-i18n-support/1.0.39_6/json-schema-validator-i18n-support-1.0.39_6.jar deleted file mode 100644 index b37291a98cfdd934d6ad4b1adeeb11b0f84b2dff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195706 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 #?P9Lj(j!o;Ym`m}14) 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ǥ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 - - 4.0.0 - com.networknt - json-schema-validator-i18n-support - 1.0.39_6 - POM was created from install:install-file - JsonSchemaValidator - - 1.8 - 1.8 - UTF-8 - 2.10.0 - 1.7.25 - 3.5 - 2.1.31 - 1.2.3 - 4.12 - 2.7.21 - 1.3 - 2.0.29.Final - - - - com.fasterxml.jackson.core - jackson-databind - ${version.jackson} - - - org.slf4j - slf4j-api - ${version.slf4j} - - - org.apache.commons - commons-lang3 - ${version.common-lang3} - - - org.jruby.joni - joni - ${version.joni} - - - ch.qos.logback - logback-classic - ${version.logback} - test - - - junit - junit - ${version.junit} - test - - - org.mockito - mockito-core - ${version.mockito} - test - - - org.hamcrest - hamcrest-all - ${version.hamcrest} - test - - - io.undertow - undertow-core - ${version.undertow} - test - - - diff --git a/repo/com/networknt/json-schema-validator-i18n-support/maven-metadata-local.xml b/repo/com/networknt/json-schema-validator-i18n-support/maven-metadata-local.xml deleted file mode 100644 index 614a6a4..0000000 --- a/repo/com/networknt/json-schema-validator-i18n-support/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - com.networknt - json-schema-validator-i18n-support - - 1.0.39_6 - - 1.0.39_6 - - 20210430081305 - - From 1084c477436b3045b01ca4e13b7774642e7f5bcb Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Wed, 28 Jun 2023 14:23:38 +0800 Subject: [PATCH 08/14] update gateway prefix to / --- fizz-bootstrap/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fizz-bootstrap/src/main/resources/application.yml b/fizz-bootstrap/src/main/resources/application.yml index 9bd105e..28f67c1 100644 --- a/fizz-bootstrap/src/main/resources/application.yml +++ b/fizz-bootstrap/src/main/resources/application.yml @@ -93,7 +93,7 @@ flow-stat-sched: queue: fizz_resource_access_stat gateway: - prefix: /proxy + prefix: / aggr: # set headers when calling the backend API proxy_set_headers: X-Real-IP,X-Forwarded-Proto,X-Forwarded-For From d17a13b184a7df9757116b0a31fa4609b394501b Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Wed, 28 Jun 2023 16:57:50 +0800 Subject: [PATCH 09/14] support rejecting all requests by ratelimit --- fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java b/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java index 919177a..b87c81d 100644 --- a/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java +++ b/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java @@ -161,10 +161,10 @@ public class FlowStat { for (ResourceConfig resourceConfig : resourceConfigs) { long maxCon = resourceConfig.getMaxCon(); long maxQPS = resourceConfig.getMaxQPS(); - if (maxCon > 0 || maxQPS > 0) { + if (maxCon >= 0 || maxQPS >= 0) { ResourceStat resourceStat = getResourceStat(resourceConfig.getResourceId()); // check concurrent request - if (maxCon > 0) { + if (maxCon >= 0) { long n = resourceStat.getConcurrentRequests().get(); if (n >= maxCon) { resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId); @@ -184,7 +184,7 @@ public class FlowStat { } // check QPS - if (maxQPS > 0) { + if (maxQPS >= 0) { long total = resourceStat.getTimeSlot(curTimeSlotId).getCounter(); if (total >= maxQPS) { resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId); From b92dd42c8e5f350bd9ef94fbfb529c936f090962 Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Thu, 29 Jun 2023 11:34:48 +0800 Subject: [PATCH 10/14] support rejecting all requests by ratelimit --- .../main/java/com/fizzgate/stats/ResourceConfig.java | 8 ++++---- .../stats/ratelimit/ResourceRateLimitConfig.java | 4 ++-- .../test/java/com/fizzgate/stats/FlowStatTests.java | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/fizz-core/src/main/java/com/fizzgate/stats/ResourceConfig.java b/fizz-core/src/main/java/com/fizzgate/stats/ResourceConfig.java index 8c2b22f..651a185 100644 --- a/fizz-core/src/main/java/com/fizzgate/stats/ResourceConfig.java +++ b/fizz-core/src/main/java/com/fizzgate/stats/ResourceConfig.java @@ -46,14 +46,14 @@ public class ResourceConfig { // Flow control rule //--------------------------------------------------------------------- /** - * Maximum concurrent request, zero or negative for no limit + * Maximum concurrent request, negative for no limit */ - private long maxCon; + private long maxCon = -1L; /** - * Maximum QPS, zero or negative for no limit + * Maximum QPS, negative for no limit */ - private long maxQPS; + private long maxQPS = -1L; //--------------------------------------------------------------------- diff --git a/fizz-core/src/main/java/com/fizzgate/stats/ratelimit/ResourceRateLimitConfig.java b/fizz-core/src/main/java/com/fizzgate/stats/ratelimit/ResourceRateLimitConfig.java index 9eaf39b..24453e1 100644 --- a/fizz-core/src/main/java/com/fizzgate/stats/ratelimit/ResourceRateLimitConfig.java +++ b/fizz-core/src/main/java/com/fizzgate/stats/ratelimit/ResourceRateLimitConfig.java @@ -63,9 +63,9 @@ public class ResourceRateLimitConfig { public byte type; - public long qps; + public long qps = -1L; - public long concurrents; + public long concurrents = -1L; public String responseType; diff --git a/fizz-core/src/test/java/com/fizzgate/stats/FlowStatTests.java b/fizz-core/src/test/java/com/fizzgate/stats/FlowStatTests.java index 0b797cc..95ac803 100644 --- a/fizz-core/src/test/java/com/fizzgate/stats/FlowStatTests.java +++ b/fizz-core/src/test/java/com/fizzgate/stats/FlowStatTests.java @@ -115,8 +115,8 @@ public class FlowStatTests { // Note: use different resource ID to avoid being affected by previous test data FlowRuleCase c5 = new FlowRuleCase(); - c5.resourceConfigs.add(new ResourceConfig("_global5", 0, 0)); - c5.resourceConfigs.add(new ResourceConfig("service5", 0, 0)); + c5.resourceConfigs.add(new ResourceConfig("_global5", -1L, -1L)); + c5.resourceConfigs.add(new ResourceConfig("service5", -1L, -1L)); c5.resourceExpects.add(new ResourceExpect(c5.totalReqs, c5.totalReqs, c5.totalReqs, 0)); c5.resourceExpects.add(new ResourceExpect(c5.totalReqs, c5.totalReqs, c5.totalReqs, 0)); c5.expectResult = IncrRequestResult.success(); @@ -124,8 +124,8 @@ public class FlowStatTests { // Note: use different resource ID to avoid being affected by previous test data FlowRuleCase c6 = new FlowRuleCase(); - c6.resourceConfigs.add(new ResourceConfig("_global6", 20, 0)); - c6.resourceConfigs.add(new ResourceConfig("service6", 20, 0)); + c6.resourceConfigs.add(new ResourceConfig("_global6", 20, -1L)); + c6.resourceConfigs.add(new ResourceConfig("service6", 20, -1L)); c6.resourceExpects.add(new ResourceExpect(20, 20, 20, c6.totalReqs - 20)); c6.resourceExpects.add(new ResourceExpect(20, 20, 20, 0)); c6.expectResult = IncrRequestResult.block("_global6", BlockType.CONCURRENT_REQUEST); @@ -133,8 +133,8 @@ public class FlowStatTests { // Note: use different resource ID to avoid being affected by previous test data FlowRuleCase c7 = new FlowRuleCase(); - c7.resourceConfigs.add(new ResourceConfig("_global7", 0, 0)); - c7.resourceConfigs.add(new ResourceConfig("service7", 0, 20)); + c7.resourceConfigs.add(new ResourceConfig("_global7", -1L, -1L)); + c7.resourceConfigs.add(new ResourceConfig("service7", -1L, 20)); c7.resourceExpects.add(new ResourceExpect(20, 20, 20, 0)); c7.resourceExpects.add(new ResourceExpect(20, 20, 20, c7.totalReqs - 20)); c7.expectResult = IncrRequestResult.block("service7", BlockType.QPS); From 926cacb60bf6c9b6bd9e0cbbaa73754208a3a9c2 Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Thu, 29 Jun 2023 16:54:44 +0800 Subject: [PATCH 11/14] support rejecting all requests by ratelimit --- fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java b/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java index b87c81d..bba3b0f 100644 --- a/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java +++ b/fizz-core/src/main/java/com/fizzgate/stats/FlowStat.java @@ -228,10 +228,10 @@ public class FlowStat { for (ResourceConfig resourceConfig : resourceConfigs) { long maxCon = resourceConfig.getMaxCon(); long maxQPS = resourceConfig.getMaxQPS(); - if (maxCon > 0 || maxQPS > 0) { + if (maxCon >= 0 || maxQPS >= 0) { ResourceStat resourceStat = getResourceStat(resourceConfig.getResourceId()); // check concurrent request - if (maxCon > 0) { + if (maxCon >= 0) { long n = resourceStat.getConcurrentRequests().get(); if (n >= maxCon) { resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId); @@ -251,7 +251,7 @@ public class FlowStat { } // check QPS - if (maxQPS > 0) { + if (maxQPS >= 0) { long total = resourceStat.getTimeSlot(curTimeSlotId).getCounter(); if (total >= maxQPS) { resourceStat.incrBlockRequestToTimeSlot(curTimeSlotId); From 6eee0c2e80add4cf0f09da0309634a95dae74ead Mon Sep 17 00:00:00 2001 From: Francis Dong Date: Thu, 29 Jun 2023 17:19:20 +0800 Subject: [PATCH 12/14] support rejecting all requests by ratelimit --- .../java/com/fizzgate/filter/FlowControlFilter.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fizz-core/src/main/java/com/fizzgate/filter/FlowControlFilter.java b/fizz-core/src/main/java/com/fizzgate/filter/FlowControlFilter.java index 8eb7bd3..e68843a 100644 --- a/fizz-core/src/main/java/com/fizzgate/filter/FlowControlFilter.java +++ b/fizz-core/src/main/java/com/fizzgate/filter/FlowControlFilter.java @@ -360,7 +360,7 @@ public class FlowControlFilter extends FizzWebFilter { if (hasHost) { // String resourceId = ResourceIdUtils.buildResourceId(app, ip, node, service, path); String resourceId = ResourceIdUtils.buildResourceId(null, null, node, null, null); - ResourceConfig resourceConfig = new ResourceConfig(resourceId, 0, 0); + ResourceConfig resourceConfig = new ResourceConfig(resourceId, -1L, -1L); resourceConfigs.add(resourceConfig); } checkRateLimitConfigAndAddTo(resourceConfigs, b, null, null, ResourceIdUtils.NODE, null, null, null); @@ -407,11 +407,11 @@ public class FlowControlFilter extends FizzWebFilter { } else { String node = ResourceIdUtils.getNode(resource); if (node != null && node.equals(ResourceIdUtils.NODE)) { - rc = new ResourceConfig(resource, 0, 0); + rc = new ResourceConfig(resource, -1L, -1L); } if (defaultRateLimitConfigId != null) { if (defaultRateLimitConfigId.equals(ResourceIdUtils.SERVICE_DEFAULT)) { - rc = new ResourceConfig(resource, 0, 0); + rc = new ResourceConfig(resource, -1L, -1L); rateLimitConfig = resourceRateLimitConfigService.getResourceRateLimitConfig(ResourceIdUtils.SERVICE_DEFAULT_RESOURCE); if (rateLimitConfig != null && rateLimitConfig.isEnable()) { rc.setMaxCon(rateLimitConfig.concurrents); @@ -441,7 +441,7 @@ public class FlowControlFilter extends FizzWebFilter { } }*/ if (cb != null) { - rc = new ResourceConfig(resource, 0, 0); + rc = new ResourceConfig(resource, -1L, -1L); resourceConfigs.add(rc); } } @@ -508,7 +508,7 @@ public class FlowControlFilter extends FizzWebFilter { private void something4(List resourceConfigs, String app, String ip, String service) { String r = ResourceIdUtils.buildResourceId(app, ip, null, service, null); - ResourceConfig rc = new ResourceConfig(r, 0, 0); + ResourceConfig rc = new ResourceConfig(r, -1L, -1L); resourceConfigs.add(rc); } From 3a50d6c47a88a8bfba11d2f3bfb4cd20836e3a4a Mon Sep 17 00:00:00 2001 From: hongqiaowei Date: Fri, 7 Jul 2023 18:05:46 +0800 Subject: [PATCH 13/14] Remove GrayReleasePluginTests --- .../grayrelease/GrayReleasePluginTests.java | 398 +++++++++--------- 1 file changed, 199 insertions(+), 199 deletions(-) diff --git a/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java b/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java index 5a8dd6d..059399e 100644 --- a/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java +++ b/fizz-plugin/src/test/java/com/fizzgate/plugin/grayrelease/GrayReleasePluginTests.java @@ -1,199 +1,199 @@ -package com.fizzgate.plugin.grayrelease; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fizzgate.aggregate.web.filter.AggregateFilter; -import com.fizzgate.aggregate.web.loader.ConfigLoader; -import com.fizzgate.filter.FilterResult; -import com.fizzgate.plugin.FizzPluginFilterChain; -import com.fizzgate.plugin.auth.ApiConfig; -import com.fizzgate.proxy.Route; -import com.fizzgate.util.Consts; -import com.fizzgate.util.JacksonUtils; -import com.fizzgate.util.ReflectionUtils; -import com.fizzgate.util.WebUtils; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.test.web.reactive.server.WebTestClient; -import reactor.core.publisher.Mono; - -import java.util.HashMap; -import java.util.Map; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class GrayReleasePluginTests { - - /** - * service discovery backend - */ - @Test - public void simpleTest() { - final Route[] changedRoute = new Route[1]; - WebTestClient client = WebTestClient.bindToWebHandler( - exchange -> { - ServerHttpResponse r = exchange.getResponse(); - r.setStatusCode(HttpStatus.OK); - r.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); - return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes()))); - } - ) - .webFilter( - (exchange, chain) -> { - - GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin(); - Map config = new HashMap<>(); - config.put("triggerCondition", " method == 'post' " + - " and matches('path','^/apath/x*') " + - " and clientIpInRange('11.238.145.180', '11.238.145.182') " + - " and exist('body.tools.gun') "); - config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.SERVICE_DISCOVERY))); - config.put("routeConfig", - "type : http \n " + - "serviceName : bservice \n " + - "path : /bpath/{$1} "); - - // exchange.getAttributes().put("pcsit@", Collections.emptyIterator()); - Route route = new Route().path("/apath/**"); - changedRoute[0] = route; - exchange.getAttributes().put(WebUtils.ROUTE, route); - exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY); - exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain); - exchange.getAttributes().put("oi@", "11.238.145.181"); - - return grayReleasePlugin.filter(exchange, config); - } - ) - .build(); - - client.post() - .uri("/aservice/apath/xxx") - .contentType(MediaType.APPLICATION_JSON) - .bodyValue("{\"user\":\"henry\",\"tools\":{\"gun\":\"ak\"}}") - .exchange() - .expectBody(String.class).value( - v -> { - // System.err.println("body:\n" + v); - } - ); - - Assertions.assertEquals("bservice", changedRoute[0].backendService); - Assertions.assertEquals("/bpath/xxx", changedRoute[0].backendPath); - } - - @Test - public void reverseProxyBackendTest() { - - final Route[] changedRoute = new Route[1]; - - Map config = new HashMap<>(); - config.put("triggerCondition", " method == 'get' "); - config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.REVERSE_PROXY))); - config.put("routeConfig", - "serviceName : http://1.2.3.4:8080,http://1.2.3.5:8080 \n " + - "path : /a/b/c \n" + - "query : name1=value1&name2=value2 "); - - WebTestClient client = WebTestClient.bindToWebHandler( - exchange -> { - ServerHttpResponse r = exchange.getResponse(); - r.setStatusCode(HttpStatus.OK); - r.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); - return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes()))); - } - ) - .webFilter( - (exchange, chain) -> { - - GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin(); - - Route route = new Route().path("/apath/**"); - changedRoute[0] = route; - exchange.getAttributes().put(WebUtils.ROUTE, route); - exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY); - exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain); - exchange.getAttributes().put("oi@", "11.238.145.181"); - - return grayReleasePlugin.filter(exchange, config); - } - ) - .build(); - - client.get() - .uri("/aservice/apath/xxx") - .exchange(); - Assertions.assertEquals("/a/b/c?name1=value1&name2=value2", changedRoute[0].getBackendPathQuery()); - Assertions.assertEquals("http://1.2.3.4:8080", changedRoute[0].nextHttpHostPort); - - client.get() - .uri("/aservice/apath/xxx") - .exchange(); - Assertions.assertEquals("http://1.2.3.5:8080", changedRoute[0].nextHttpHostPort); - - client.get() - .uri("/aservice/apath/xxx") - .exchange(); - Assertions.assertEquals("http://1.2.3.4:8080", changedRoute[0].nextHttpHostPort); - } - - @Test - public void aggregateBackendTest() { - AggregateFilter aggregateFilter = new AggregateFilter(null, null, null); - ConfigLoader configLoader = mock(ConfigLoader.class); - when( - configLoader.matchAggregateResource("GET", "/_proxytest/bservice/bpath/xxx") - ) - .thenReturn(null); - ReflectionUtils.set(aggregateFilter, "configLoader", configLoader); - - WebTestClient client = WebTestClient.bindToWebHandler( - exchange -> { - ServerHttpResponse r = exchange.getResponse(); - r.setStatusCode(HttpStatus.OK); - return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes()))); - } - ) - .webFilter( - (exchange, chain) -> { - - GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin(); - Map config = new HashMap<>(); - config.put("triggerCondition", " method == 'get' "); - config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.SERVICE_AGGREGATE))); - config.put("routeConfig", - "type : http \n " + - "serviceName : bservice \n " + - "path : /bpath/{$1} "); - - Route route = new Route().path("/apath/**"); - exchange.getAttributes().put(WebUtils.ROUTE, route); - exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY); - exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain); - exchange.getAttributes().put("oi@", "11.238.145.181"); - - Map filterContext = new HashMap<>(); - exchange.getAttributes().put(WebUtils.FILTER_CONTEXT, filterContext); - filterContext.put(WebUtils.PREV_FILTER_RESULT, FilterResult.SUCCESS("x")); - - return grayReleasePlugin.filter(exchange, config); - }, - aggregateFilter - ) - .build(); - - client.get() - .uri("/_proxytest/aservice/apath/xxx") - .exchange() - .expectBody(String.class).value( - v -> { - Map bodyMap = JacksonUtils.readValue(v, new TypeReference>(){}); - Assertions.assertEquals(bodyMap.get("message"), "API not found in aggregation: /_proxytest/bservice/bpath/xxx"); - } - ); - } -} +// package com.fizzgate.plugin.grayrelease; +// +// import com.fasterxml.jackson.core.type.TypeReference; +// import com.fizzgate.aggregate.web.filter.AggregateFilter; +// import com.fizzgate.aggregate.web.loader.ConfigLoader; +// import com.fizzgate.filter.FilterResult; +// import com.fizzgate.plugin.FizzPluginFilterChain; +// import com.fizzgate.plugin.auth.ApiConfig; +// import com.fizzgate.proxy.Route; +// import com.fizzgate.util.Consts; +// import com.fizzgate.util.JacksonUtils; +// import com.fizzgate.util.ReflectionUtils; +// import com.fizzgate.util.WebUtils; +// +// import org.junit.jupiter.api.Assertions; +// import org.junit.jupiter.api.Test; +// import org.springframework.http.HttpHeaders; +// import org.springframework.http.HttpStatus; +// import org.springframework.http.MediaType; +// import org.springframework.http.server.reactive.ServerHttpResponse; +// import org.springframework.test.web.reactive.server.WebTestClient; +// import reactor.core.publisher.Mono; +// +// import java.util.HashMap; +// import java.util.Map; +// +// import static org.mockito.Mockito.mock; +// import static org.mockito.Mockito.when; +// +// public class GrayReleasePluginTests { +// +// /** +// * service discovery backend +// */ +// @Test +// public void simpleTest() { +// final Route[] changedRoute = new Route[1]; +// WebTestClient client = WebTestClient.bindToWebHandler( +// exchange -> { +// ServerHttpResponse r = exchange.getResponse(); +// r.setStatusCode(HttpStatus.OK); +// r.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); +// return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes()))); +// } +// ) +// .webFilter( +// (exchange, chain) -> { +// +// GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin(); +// Map config = new HashMap<>(); +// config.put("triggerCondition", " method == 'post' " + +// " and matches('path','^/apath/x*') " + +// " and clientIpInRange('11.238.145.180', '11.238.145.182') " + +// " and exist('body.tools.gun') "); +// config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.SERVICE_DISCOVERY))); +// config.put("routeConfig", +// "type : http \n " + +// "serviceName : bservice \n " + +// "path : /bpath/{$1} "); +// +// // exchange.getAttributes().put("pcsit@", Collections.emptyIterator()); +// Route route = new Route().path("/apath/**"); +// changedRoute[0] = route; +// exchange.getAttributes().put(WebUtils.ROUTE, route); +// exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY); +// exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain); +// exchange.getAttributes().put("oi@", "11.238.145.181"); +// +// return grayReleasePlugin.filter(exchange, config); +// } +// ) +// .build(); +// +// client.post() +// .uri("/aservice/apath/xxx") +// .contentType(MediaType.APPLICATION_JSON) +// .bodyValue("{\"user\":\"henry\",\"tools\":{\"gun\":\"ak\"}}") +// .exchange() +// .expectBody(String.class).value( +// v -> { +// // System.err.println("body:\n" + v); +// } +// ); +// +// Assertions.assertEquals("bservice", changedRoute[0].backendService); +// Assertions.assertEquals("/bpath/xxx", changedRoute[0].backendPath); +// } +// +// @Test +// public void reverseProxyBackendTest() { +// +// final Route[] changedRoute = new Route[1]; +// +// Map config = new HashMap<>(); +// config.put("triggerCondition", " method == 'get' "); +// config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.REVERSE_PROXY))); +// config.put("routeConfig", +// "serviceName : http://1.2.3.4:8080,http://1.2.3.5:8080 \n " + +// "path : /a/b/c \n" + +// "query : name1=value1&name2=value2 "); +// +// WebTestClient client = WebTestClient.bindToWebHandler( +// exchange -> { +// ServerHttpResponse r = exchange.getResponse(); +// r.setStatusCode(HttpStatus.OK); +// r.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); +// return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes()))); +// } +// ) +// .webFilter( +// (exchange, chain) -> { +// +// GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin(); +// +// Route route = new Route().path("/apath/**"); +// changedRoute[0] = route; +// exchange.getAttributes().put(WebUtils.ROUTE, route); +// exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY); +// exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain); +// exchange.getAttributes().put("oi@", "11.238.145.181"); +// +// return grayReleasePlugin.filter(exchange, config); +// } +// ) +// .build(); +// +// client.get() +// .uri("/aservice/apath/xxx") +// .exchange(); +// Assertions.assertEquals("/a/b/c?name1=value1&name2=value2", changedRoute[0].getBackendPathQuery()); +// Assertions.assertEquals("http://1.2.3.4:8080", changedRoute[0].nextHttpHostPort); +// +// client.get() +// .uri("/aservice/apath/xxx") +// .exchange(); +// Assertions.assertEquals("http://1.2.3.5:8080", changedRoute[0].nextHttpHostPort); +// +// client.get() +// .uri("/aservice/apath/xxx") +// .exchange(); +// Assertions.assertEquals("http://1.2.3.4:8080", changedRoute[0].nextHttpHostPort); +// } +// +// @Test +// public void aggregateBackendTest() { +// AggregateFilter aggregateFilter = new AggregateFilter(null, null, null); +// ConfigLoader configLoader = mock(ConfigLoader.class); +// when( +// configLoader.matchAggregateResource("GET", "/_proxytest/bservice/bpath/xxx") +// ) +// .thenReturn(null); +// ReflectionUtils.set(aggregateFilter, "configLoader", configLoader); +// +// WebTestClient client = WebTestClient.bindToWebHandler( +// exchange -> { +// ServerHttpResponse r = exchange.getResponse(); +// r.setStatusCode(HttpStatus.OK); +// return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes()))); +// } +// ) +// .webFilter( +// (exchange, chain) -> { +// +// GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin(); +// Map config = new HashMap<>(); +// config.put("triggerCondition", " method == 'get' "); +// config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.SERVICE_AGGREGATE))); +// config.put("routeConfig", +// "type : http \n " + +// "serviceName : bservice \n " + +// "path : /bpath/{$1} "); +// +// Route route = new Route().path("/apath/**"); +// exchange.getAttributes().put(WebUtils.ROUTE, route); +// exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY); +// exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain); +// exchange.getAttributes().put("oi@", "11.238.145.181"); +// +// Map filterContext = new HashMap<>(); +// exchange.getAttributes().put(WebUtils.FILTER_CONTEXT, filterContext); +// filterContext.put(WebUtils.PREV_FILTER_RESULT, FilterResult.SUCCESS("x")); +// +// return grayReleasePlugin.filter(exchange, config); +// }, +// aggregateFilter +// ) +// .build(); +// +// client.get() +// .uri("/_proxytest/aservice/apath/xxx") +// .exchange() +// .expectBody(String.class).value( +// v -> { +// Map bodyMap = JacksonUtils.readValue(v, new TypeReference>(){}); +// Assertions.assertEquals(bodyMap.get("message"), "API not found in aggregation: /_proxytest/bservice/bpath/xxx"); +// } +// ); +// } +// } From 4f473aaf122078ea6133557a256095f9f608caa6 Mon Sep 17 00:00:00 2001 From: "nil.zhong" Date: Mon, 10 Jul 2023 10:54:11 +0800 Subject: [PATCH 14/14] Upgrade fizz-aggregate-spring-boot-starter to 1.0.0 --- fizz-core/pom.xml | 670 +++++++++++++++++++++++----------------------- 1 file changed, 335 insertions(+), 335 deletions(-) diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml index 76aac1f..e110935 100644 --- a/fizz-core/pom.xml +++ b/fizz-core/pom.xml @@ -1,336 +1,336 @@ - - - - fizz-gateway-community - com.fizzgate - 3.0.0-SNAPSHOT - ../pom.xml - - 4.0.0 - - fizz-core - - - 1.0.0-SNAPSHOT - - - - - com.fizzgate - fizz-aggregate-spring-boot-starter - ${aggregate.version} - - - com.fizzgate - fizz-core - - - - - - com.lmax - disruptor - - - - com.fizzgate - fizz-common - - - - org.springframework.session - spring-session-bom - Dragonfruit-SR3 - pom - import - - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.boot - spring-boot-starter-logging - - - - - - org.springframework.boot - spring-boot-starter-log4j2 - - - ch.qos.logback - logback-classic - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - org.skyscreamer - jsonassert - - - - - - io.projectreactor - reactor-test - test - - - - com.alibaba - fastjson - - - - com.google.code.gson - gson - - - - net.minidev - json-smart - - - - org.codehaus.jettison - jettison - - - - org.json - json - - - - org.apache.tapestry - tapestry-json - - - - org.apache.commons - commons-lang3 - - - - org.codehaus.groovy - groovy-all - - - - commons-io - commons-io - - - - com.ctrip.framework.apollo - apollo-client - - - - org.springframework.cloud - spring-cloud-starter-netflix-eureka-client - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.apache.commons - commons-pool2 - - - - com.github.ben-manes.caffeine - caffeine - - - error_prone_annotations - com.google.errorprone - - - - - - org.springframework.boot - spring-boot-starter-data-redis-reactive - - - - it.ozimov - embedded-redis - test - - - org.slf4j - slf4j-simple - - - - - - org.noear - snack3 - - - - org.springframework.session - spring-session-data-redis - - - - com.auth0 - java-jwt - - - - org.mockito - mockito-core - test - - - - org.mockito - mockito-inline - test - - - - org.apache.dubbo - dubbo - - - - - io.grpc - grpc-all - - - io.grpc - grpc-services - - - org.projectlombok - lombok - - - - - org.reflections - reflections - ${reflections.version} - - - - org.apache.curator - curator-client - - - org.apache.curator - curator-framework - - - org.apache.curator - curator-recipes - - - org.apache.zookeeper - zookeeper - - - org.slf4j - slf4j-log4j12 - - - log4j - log4j - - - - - - junit - junit - test - - - - org.bouncycastle - bcpkix-jdk15on - - - - com.google.guava - guava - - - - commons-codec - commons-codec - - - - commons-beanutils - commons-beanutils - - - - io.netty - netty-tcnative - - - io.netty - netty-tcnative-boringssl-static - - - io.netty - netty-tcnative-classes - - - - - - sonatype-snapshots - SonaType Snapshots - https://oss.sonatype.org/content/repositories/snapshots - - true - - - - - - - - org.apache.maven.plugins - maven-source-plugin - - - + + + + fizz-gateway-community + com.fizzgate + 3.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + + fizz-core + + + 1.0.0 + + + + + com.fizzgate + fizz-aggregate-spring-boot-starter + ${aggregate.version} + + + com.fizzgate + fizz-core + + + + + + com.lmax + disruptor + + + + com.fizzgate + fizz-common + + + + org.springframework.session + spring-session-bom + Dragonfruit-SR3 + pom + import + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + ch.qos.logback + logback-classic + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + org.skyscreamer + jsonassert + + + + + + io.projectreactor + reactor-test + test + + + + com.alibaba + fastjson + + + + com.google.code.gson + gson + + + + net.minidev + json-smart + + + + org.codehaus.jettison + jettison + + + + org.json + json + + + + org.apache.tapestry + tapestry-json + + + + org.apache.commons + commons-lang3 + + + + org.codehaus.groovy + groovy-all + + + + commons-io + commons-io + + + + com.ctrip.framework.apollo + apollo-client + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + org.apache.commons + commons-pool2 + + + + com.github.ben-manes.caffeine + caffeine + + + error_prone_annotations + com.google.errorprone + + + + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + + + + it.ozimov + embedded-redis + test + + + org.slf4j + slf4j-simple + + + + + + org.noear + snack3 + + + + org.springframework.session + spring-session-data-redis + + + + com.auth0 + java-jwt + + + + org.mockito + mockito-core + test + + + + org.mockito + mockito-inline + test + + + + org.apache.dubbo + dubbo + + + + + io.grpc + grpc-all + + + io.grpc + grpc-services + + + org.projectlombok + lombok + + + + + org.reflections + reflections + ${reflections.version} + + + + org.apache.curator + curator-client + + + org.apache.curator + curator-framework + + + org.apache.curator + curator-recipes + + + org.apache.zookeeper + zookeeper + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + + + + junit + junit + test + + + + org.bouncycastle + bcpkix-jdk15on + + + + com.google.guava + guava + + + + commons-codec + commons-codec + + + + commons-beanutils + commons-beanutils + + + + io.netty + netty-tcnative + + + io.netty + netty-tcnative-boringssl-static + + + io.netty + netty-tcnative-classes + + + + + + sonatype-snapshots + SonaType Snapshots + https://oss.sonatype.org/content/repositories/snapshots + + true + + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + \ No newline at end of file